/**
 * jsPanel - A JavaScript library to create highly configurable multifunctional floating panels that can also be used as modal, tooltip, hint or contextmenu
 * @version v4.16.1
 * @homepage https://jspanel.de/
 * @license MIT
 * @author Stefan Sträßer - info@jspanel.de
 * @author of dialog extension: Michael Daumling - michael@terrapinlogo.com
 * @github https://github.com/Flyer53/jsPanel4.git
 */

export // eslint-disable-next-line no-redeclare
// noinspection JSVoidFunctionReturnValueUsed
// eslint-disable-next-line no-redeclare
let jsPanel = {
    version: '4.16.1',
    date: '2022-11-03 09:18',
    ajaxAlwaysCallbacks: [],
    autopositionSpacing: 4,
    closeOnEscape: (() => {
        document.addEventListener('keydown', e => {
            if (e.key === 'Escape' || e.code === 'Escape' || e.key === 'Esc') {
                jsPanel
                    .getPanels(panel => panel.classList.contains('jsPanel')) // Array is sorted by z-index (the highest first)
                    .some(item => {
                        if (item.options.closeOnEscape) {
                            if (typeof item.options.closeOnEscape === 'function') {
                                return item.options.closeOnEscape.call(item, item);
                                // if return value is falsy next panel in sequence will close, otherwise processing of Array.prototype.some() stops
                            } else {
                                item.close(null, true);
                                return true;
                            }
                        }
                        return false;
                    });
            }
        }, false);
    })(),
    defaults: {
        boxShadow: 3,
        container: 'window',
        contentSize: { width: '400px', height: '200px' }, // must be an object
        dragit: {
            cursor: 'move',
            handles: '.jsPanel-headerlogo, .jsPanel-titlebar, .jsPanel-ftr', // do not use .jsPanel-headerbar
            opacity: 0.8,
            disableOnMaximized: true,
        },
        header: true,
        headerTitle: 'jsPanel',
        headerControls: {size: 'md'}, // must be an object
        iconfont: undefined,
        maximizedMargin: 0,
        minimizeTo: 'default',
        paneltype: 'standard',
        position: {my: 'center', at: 'center'}, // default position.of MUST NOT be set with new positioning engine
        resizeit: {
            handles: 'n, e, s, w, ne, se, sw, nw',
            minWidth: 128,
            minHeight: 38,
        },
        theme: 'default'
    },
    defaultAutocloseConfig: {time: '8s', progressbar: true},
    defaultSnapConfig: {
        sensitivity: 70,
        trigger: 'panel',
        active: 'both',
    },
    extensions: {},
    globalCallbacks: false,
    icons: {
        close: `<svg focusable="false" class="jsPanel-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22"><path fill="currentColor" d="M13.7,11l6.1-6.1c0.4-0.4,0.4-0.9,0-1.3l-1.4-1.4c-0.4-0.4-0.9-0.4-1.3,0L11,8.3L4.9,2.3C4.6,1.9,4,1.9,3.7,2.3L2.3,3.7 C1.9,4,1.9,4.6,2.3,4.9L8.3,11l-6.1,6.1c-0.4,0.4-0.4,0.9,0,1.3l1.4,1.4c0.4,0.4,0.9,0.4,1.3,0l6.1-6.1l6.1,6.1 c0.4,0.4,0.9,0.4,1.3,0l1.4-1.4c0.4-0.4,0.4-0.9,0-1.3L13.7,11z"/></svg>`,
        maximize: `<svg focusable="false" class="jsPanel-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22"><path fill="currentColor" d="M18.3,2H3.7C2.8,2,2,2.9,2,3.9v14.1C2,19.1,2.8,20,3.7,20h14.6c0.9,0,1.7-0.9,1.7-1.9V3.9C20,2.9,19.2,2,18.3,2z M18.3,17.8 c0,0.1-0.1,0.2-0.2,0.2H3.9c-0.1,0-0.2-0.1-0.2-0.2V8.4h14.6V17.8z"/></svg>`,
        normalize: `<svg focusable="false" class="jsPanel-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22"><path fill="currentColor" d="M18.3,2H7.1C6.1,2,5.4,2.8,5.4,3.7v1.7H3.7C2.8,5.4,2,6.1,2,7.1v11.3C2,19.2,2.8,20,3.7,20h11.3c0.9,0,1.7-0.8,1.7-1.7v-1.7 h1.7c0.9,0,1.7-0.8,1.7-1.7V3.7C20,2.8,19.2,2,18.3,2z M14.9,18.3H3.7V11h11.3V18.3z M18.3,14.9h-1.7V7.1c0-0.9-0.8-1.7-1.7-1.7H7.1 V3.7h11.3V14.9z"/></svg>`,
        minimize: `<svg focusable="false" class="jsPanel-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22"><path fill="currentColor" d="M18.9,19.8H3.1c-0.6,0-1.1-0.5-1.1-1.1s0.5-1.1,1.1-1.1h15.8c0.6,0,1.1,0.5,1.1,1.1S19.5,19.8,18.9,19.8z"/></svg>`,
        smallify: `<svg focusable="false" class="jsPanel-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22"><path fill="currentColor" d="M2.1,15.2L2.9,16c0.2,0.2,0.5,0.2,0.7,0L11,8.7l7.4,7.3c0.2,0.2,0.5,0.2,0.7,0l0.8-0.8c0.2-0.2,0.2-0.5,0-0.7L11.3,6 c-0.2-0.2-0.5-0.2-0.7,0l-8.5,8.5C2,14.7,2,15,2.1,15.2z"/></svg>`,
    },
    idCounter: 0,
    isIE: (() => {return document.documentMode || false;})(),
    pointerdown: 'onpointerdown' in window ? ['pointerdown'] : 'ontouchend' in window ? ['touchstart', 'mousedown'] : ['mousedown'],
    pointermove: 'onpointermove' in window ? ['pointermove'] : 'ontouchend' in window ? ['touchmove', 'mousemove'] : ['mousemove'],
    pointerup: 'onpointerup' in window ? ['pointerup'] : 'ontouchend' in window ? ['touchend', 'mouseup'] : ['mouseup'],
    polyfills: (() => {
        // Polyfills for IE11 only
        // Object.assign polyfill - https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
        if (!Object.assign) {
            Object.defineProperty(Object, 'assign', {
                enumerable: false,
                configurable: true,
                writable: true,
                value: function (target) {
                    if (target === undefined || target === null) {
                        throw new TypeError('Cannot convert first argument to object');
                    }
                    let to = Object(target);
                    for (let i = 1; i < arguments.length; i++) {
                        let nextSource = arguments[i];
                        if (nextSource === undefined || nextSource === null) {
                            continue;
                        }
                        nextSource = Object(nextSource);
                        let keysArray = Object.keys(Object(nextSource));
                        for (let nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
                            let nextKey = keysArray[nextIndex];
                            let desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
                            if (desc !== undefined && desc.enumerable) {
                                to[nextKey] = nextSource[nextKey];
                            }
                        }
                    }
                    return to;
                },
            });
        }
        // Object.entries() - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
        if (!Object.entries) {
            Object.entries = function( obj ){
                // noinspection ES6ConvertVarToLetConst
                var ownProps = Object.keys( obj ),
                    i = ownProps.length,
                    resArray = new Array(i); // preallocate the Array
                while (i--)
                    resArray[i] = [ownProps[i], obj[ownProps[i]]];

                return resArray;
            };
        }
        // NodeList.prototype.forEach() polyfill - https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach
        if (window.NodeList && !NodeList.prototype.forEach) {
            NodeList.prototype.forEach = function (callback, thisArg) {
                thisArg = thisArg || window;
                for (let i = 0; i < this.length; i++) {
                    callback.call(thisArg, this[i], i, this);
                }
            };
        }
        // .append() polyfill - https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append
        (function (arr) {
            arr.forEach(function (item) {
                item.append =
                    item.append ||
                    function () {
                        let argArr = Array.prototype.slice.call(arguments),
                            docFrag = document.createDocumentFragment();
                        argArr.forEach(function (argItem) {
                            let isNode = argItem instanceof Node;
                            docFrag.appendChild(isNode ? argItem : document.createTextNode(String(argItem)));
                        });
                        this.appendChild(docFrag);
                    };
            });
        })([Element.prototype, Document.prototype, DocumentFragment.prototype]);
        // Element.closest() polyfill - https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
        if (window.Element && !Element.prototype.closest) {
            // noinspection JSValidateTypes
            Element.prototype.closest = function (s) {
                // noinspection JSUnresolvedVariable
                let matches = (this.document || this.ownerDocument).querySelectorAll(s),
                    i,
                    el = this;
                do {
                    i = matches.length;
                    // eslint-disable-next-line no-empty
                    while (--i >= 0 && matches.item(i) !== el) {}
                } while (i < 0 && (el = el.parentElement));
                return el;
            };
        }
        // CustomEvent - https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent
        (function () {
            if (typeof window.CustomEvent === 'function') return false;
            function CustomEvent(event, params) {
                params = params || {
                    bubbles: false,
                    cancelable: false,
                    detail: undefined,
                };
                let evt = document.createEvent('CustomEvent');
                // noinspection JSDeprecatedSymbols
                evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
                return evt;
            }
            // noinspection JSValidateTypes
            CustomEvent.prototype = window.Event.prototype;
            // noinspection JSValidateTypes
            window.CustomEvent = CustomEvent;
        })();
        // String.prototype.endsWith() - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
        if (!String.prototype.endsWith) {
            String.prototype.endsWith = function(search, this_len) {
                if (this_len === undefined || this_len > this.length) {
                    this_len = this.length;
                }
                return this.substring(this_len - search.length, this_len) === search;
            };
        }
        // String.prototype.startsWith() - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
        if (!String.prototype.startsWith) {
            Object.defineProperty(String.prototype, 'startsWith', {
                value: function(search, rawPos) {
                    // noinspection ES6ConvertVarToLetConst
                    var pos = rawPos > 0 ? rawPos|0 : 0;
                    return this.substring(pos, pos + search.length) === search;
                }
            });
        }
        // String.prototype.includes() - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes
        if (!String.prototype.includes) {
            String.prototype.includes = function(search, start) {
                'use strict';
                if (search instanceof RegExp) {
                    throw TypeError('first argument must not be a RegExp');
                }
                if (start === undefined) { start = 0; }
                return this.indexOf(search, start) !== -1;
            };
        }
        // String.prototype repeat() - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
        if (!String.prototype.repeat) {
            String.prototype.repeat = function(count) {
                'use strict';
                if (this == null)
                    throw new TypeError('can\'t convert ' + this + ' to object');
                // noinspection ES6ConvertVarToLetConst
                var str = '' + this;
                count = +count;
                if (count != count)
                    count = 0;
                if (count < 0)
                    throw new RangeError('repeat count must be non-negative');
                if (count == Infinity)
                    throw new RangeError('repeat count must be less than infinity');
                count = Math.floor(count);
                if (str.length == 0 || count == 0)
                    return '';
                if (str.length * count >= 1 << 28)
                    throw new RangeError('repeat count must not overflow maximum string size');
                // noinspection ES6ConvertVarToLetConst
                var maxCount = str.length * count;
                count = Math.floor(Math.log(count) / Math.log(2));
                while (count) {
                    str += str;
                    count--;
                }
                str += str.substring(0, maxCount - str.length);
                return str;
            };
        }
        // Number.isInteger() -
        Number.isInteger =
            Number.isInteger ||
            function (value) {
                return typeof value === 'number' && isFinite(value) && Math.floor(value) === value;
            };
        // Array.prototype.includes() - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
        if (!Array.prototype.includes) {
            Object.defineProperty(Array.prototype, 'includes', {
                value: function (searchElement, fromIndex) {
                    if (this == null) {
                        throw new TypeError('"this" is null or not defined');
                    }
                    // 1. Let O be ? ToObject(this value).
                    let o = Object(this);
                    // 2. Let len be ? ToLength(? Get(O, "length")).
                    let len = o.length >>> 0;
                    // 3. If len is 0, return false.
                    if (len === 0) {
                        return false;
                    }
                    // 4. Let n be ? ToInteger(fromIndex).
                    //    (If fromIndex is undefined, this step produces the value 0.)
                    let n = fromIndex | 0;
                    // 5. If n ≥ 0, then
                    //  a. Let k be n.
                    // 6. Else n < 0,
                    //  a. Let k be len + n.
                    //  b. If k < 0, let k be 0.
                    let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
                    function sameValueZero(x, y) {
                        return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
                    }
                    // 7. Repeat, while k < len
                    while (k < len) {
                        // a. Let elementK be the result of ? Get(O, ! ToString(k)).
                        // b. If SameValueZero(searchElement, elementK) is true, return true.
                        if (sameValueZero(o[k], searchElement)) {
                            return true;
                        }
                        // c. Increase k by 1.
                        k++;
                    }
                    // 8. Return false
                    return false;
                },
            });
        }
    })(),
    ziBase: 100,
    colorFilledLight: 0.81,
    colorFilledDark: 0.08,
    colorFilled: 0,
    colorBrightnessThreshold: 0.55,
    colorNames: {
        default: 'b0bec5',      // Material Design bluegray200
        secondary: 'b0bec5',
        primary: '01579b',      // Material Design lightblue900
        info: '039be5',         // Material Design lightblue600
        success: '2e7d32',      // Material Design green800
        warning: 'f57f17',      // Material Design yellow900
        danger: 'dd2c00',       // Material Design deeporangeA700
        light: 'e0e0e0',        // Material Design gray300
        dark: '263238',         // Material Design bluegray900
        // https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords
        aliceblue: 'f0f8ff',
        antiquewhite: 'faebd7',
        aqua: '00ffff',
        aquamarine: '7fffd4',
        azure: 'f0ffff',
        beige: 'f5f5dc',
        bisque: 'ffe4c4',
        black: '000000',
        blanchedalmond: 'ffebcd',
        blue: '0000ff',
        blueviolet: '8a2be2',
        brown: 'a52a2a',
        burlywood: 'deb887',
        cadetblue: '5f9ea0',
        chartreuse: '7fff00',
        chocolate: 'd2691e',
        coral: 'ff7f50',
        cornflowerblue: '6495ed',
        cornsilk: 'fff8dc',
        crimson: 'dc143c',
        cyan: '00ffff',
        darkblue: '00008b',
        darkcyan: '008b8b',
        darkgoldenrod: 'b8860b',
        darkgray: 'a9a9a9',
        darkgrey: 'a9a9a9',
        darkgreen: '006400',
        darkkhaki: 'bdb76b',
        darkmagenta: '8b008b',
        darkolivegreen: '556b2f',
        darkorange: 'ff8c00',
        darkorchid: '9932cc',
        darkred: '8b0000',
        darksalmon: 'e9967a',
        darkseagreen: '8fbc8f',
        darkslateblue: '483d8b',
        darkslategray: '2f4f4f',
        darkslategrey: '2f4f4f',
        darkturquoise: '00ced1',
        darkviolet: '9400d3',
        deeppink: 'ff1493',
        deepskyblue: '00bfff',
        dimgray: '696969',
        dimgrey: '696969',
        dodgerblue: '1e90ff',
        firebrick: 'b22222',
        floralwhite: 'fffaf0',
        forestgreen: '228b22',
        fuchsia: 'ff00ff',
        gainsboro: 'dcdcdc',
        ghostwhite: 'f8f8ff',
        gold: 'ffd700',
        goldenrod: 'daa520',
        gray: '808080',
        grey: '808080',
        green: '008000',
        greenyellow: 'adff2f',
        honeydew: 'f0fff0',
        hotpink: 'ff69b4',
        indianred: 'cd5c5c',
        indigo: '4b0082',
        ivory: 'fffff0',
        khaki: 'f0e68c',
        lavender: 'e6e6fa',
        lavenderblush: 'fff0f5',
        lawngreen: '7cfc00',
        lemonchiffon: 'fffacd',
        lightblue: 'add8e6',
        lightcoral: 'f08080',
        lightcyan: 'e0ffff',
        lightgoldenrodyellow: 'fafad2',
        lightgray: 'd3d3d3',
        lightgrey: 'd3d3d3',
        lightgreen: '90ee90',
        lightpink: 'ffb6c1',
        lightsalmon: 'ffa07a',
        lightseagreen: '20b2aa',
        lightskyblue: '87cefa',
        lightslategray: '778899',
        lightslategrey: '778899',
        lightsteelblue: 'b0c4de',
        lightyellow: 'ffffe0',
        lime: '00ff00',
        limegreen: '32cd32',
        linen: 'faf0e6',
        magenta: 'ff00ff',
        maroon: '800000',
        mediumaquamarine: '66cdaa',
        mediumblue: '0000cd',
        mediumorchid: 'ba55d3',
        mediumpurple: '9370d8',
        mediumseagreen: '3cb371',
        mediumslateblue: '7b68ee',
        mediumspringgreen: '00fa9a',
        mediumturquoise: '48d1cc',
        mediumvioletred: 'c71585',
        midnightblue: '191970',
        mintcream: 'f5fffa',
        mistyrose: 'ffe4e1',
        moccasin: 'ffe4b5',
        navajowhite: 'ffdead',
        navy: '000080',
        oldlace: 'fdf5e6',
        olive: '808000',
        olivedrab: '6b8e23',
        orange: 'ffa500',
        orangered: 'ff4500',
        orchid: 'da70d6',
        palegoldenrod: 'eee8aa',
        palegreen: '98fb98',
        paleturquoise: 'afeeee',
        palevioletred: 'd87093',
        papayawhip: 'ffefd5',
        peachpuff: 'ffdab9',
        peru: 'cd853f',
        pink: 'ffc0cb',
        plum: 'dda0dd',
        powderblue: 'b0e0e6',
        purple: '800080',
        rebeccapurple: '663399',
        red: 'ff0000',
        rosybrown: 'bc8f8f',
        royalblue: '4169e1',
        saddlebrown: '8b4513',
        salmon: 'fa8072',
        sandybrown: 'f4a460',
        seagreen: '2e8b57',
        seashell: 'fff5ee',
        sienna: 'a0522d',
        silver: 'c0c0c0',
        skyblue: '87ceeb',
        slateblue: '6a5acd',
        slategray: '708090',
        slategrey: '708090',
        snow: 'fffafa',
        springgreen: '00ff7f',
        steelblue: '4682b4',
        tan: 'd2b48c',
        teal: '008080',
        thistle: 'd8bfd8',
        tomato: 'ff6347',
        turquoise: '40e0d0',
        violet: 'ee82ee',
        wheat: 'f5deb3',
        white: 'ffffff',
        whitesmoke: 'f5f5f5',
        yellow: 'ffff00',
        yellowgreen: '9acd32',
        /* Material Design Colors https://material.io/design/color/the-color-system.html#tools-for-picking-colors */
        grey50: 'fafafa',
        grey100: 'f5f5f5',
        grey200: 'eeeeee',
        grey300: 'e0e0e0',
        grey400: 'bdbdbd',
        grey500: '9e9e9e',
        grey600: '757575',
        grey700: '616161',
        grey800: '424242',
        grey900: '212121',
        gray50: 'fafafa',
        gray100: 'f5f5f5',
        gray200: 'eeeeee',
        gray300: 'e0e0e0',
        gray400: 'bdbdbd',
        gray500: '9e9e9e',
        gray600: '757575',
        gray700: '616161',
        gray800: '424242',
        gray900: '212121',
        bluegrey50: 'eceff1',
        bluegrey100: 'CFD8DC',
        bluegrey200: 'B0BEC5',
        bluegrey300: '90A4AE',
        bluegrey400: '78909C',
        bluegrey500: '607D8B',
        bluegrey600: '546E7A',
        bluegrey700: '455A64',
        bluegrey800: '37474F',
        bluegrey900: '263238',
        bluegray50: 'eceff1',
        bluegray100: 'CFD8DC',
        bluegray200: 'B0BEC5',
        bluegray300: '90A4AE',
        bluegray400: '78909C',
        bluegray500: '607D8B',
        bluegray600: '546E7A',
        bluegray700: '455A64',
        bluegray800: '37474F',
        bluegray900: '263238',
        red50: 'FFEBEE',
        red100: 'FFCDD2',
        red200: 'EF9A9A',
        red300: 'E57373',
        red400: 'EF5350',
        red500: 'F44336',
        red600: 'E53935',
        red700: 'D32F2F',
        red800: 'C62828',
        red900: 'B71C1C',
        reda100: 'FF8A80',
        reda200: 'FF5252',
        reda400: 'FF1744',
        reda700: 'D50000',
        pink50: 'FCE4EC',
        pink100: 'F8BBD0',
        pink200: 'F48FB1',
        pink300: 'F06292',
        pink400: 'EC407A',
        pink500: 'E91E63',
        pink600: 'D81B60',
        pink700: 'C2185B',
        pink800: 'AD1457',
        pink900: '880E4F',
        pinka100: 'FF80AB',
        pinka200: 'FF4081',
        pinka400: 'F50057',
        pinka700: 'C51162',
        purple50: 'F3E5F5',
        purple100: 'E1BEE7',
        purple200: 'CE93D8',
        purple300: 'BA68C8',
        purple400: 'AB47BC',
        purple500: '9C27B0',
        purple600: '8E24AA',
        purple700: '7B1FA2',
        purple800: '6A1B9A',
        purple900: '4A148C',
        purplea100: 'EA80FC',
        purplea200: 'E040FB',
        purplea400: 'D500F9',
        purplea700: 'AA00FF',
        deeppurple50: 'EDE7F6',
        deeppurple100: 'D1C4E9',
        deeppurple200: 'B39DDB',
        deeppurple300: '9575CD',
        deeppurple400: '7E57C2',
        deeppurple500: '673AB7',
        deeppurple600: '5E35B1',
        deeppurple700: '512DA8',
        deeppurple800: '4527A0',
        deeppurple900: '311B92',
        deeppurplea100: 'B388FF',
        deeppurplea200: '7C4DFF',
        deeppurplea400: '651FFF',
        deeppurplea700: '6200EA',
        indigo50: 'E8EAF6',
        indigo100: 'C5CAE9',
        indigo200: '9FA8DA',
        indigo300: '7986CB',
        indigo400: '5C6BC0',
        indigo500: '3F51B5',
        indigo600: '3949AB',
        indigo700: '303F9F',
        indigo800: '283593',
        indigo900: '1A237E',
        indigoa100: '8C9EFF',
        indigoa200: '536DFE',
        indigoa400: '3D5AFE',
        indigoa700: '304FFE',
        blue50: 'E3F2FD',
        blue100: 'BBDEFB',
        blue200: '90CAF9',
        blue300: '64B5F6',
        blue400: '42A5F5',
        blue500: '2196F3',
        blue600: '1E88E5',
        blue700: '1976D2',
        blue800: '1565C0',
        blue900: '0D47A1',
        bluea100: '82B1FF',
        bluea200: '448AFF',
        bluea400: '2979FF',
        bluea700: '2962FF',
        lightblue50: 'E1F5FE',
        lightblue100: 'B3E5FC',
        lightblue200: '81D4FA',
        lightblue300: '4FC3F7',
        lightblue400: '29B6F6',
        lightblue500: '03A9F4',
        lightblue600: '039BE5',
        lightblue700: '0288D1',
        lightblue800: '0277BD',
        lightblue900: '01579B',
        lightbluea100: '80D8FF',
        lightbluea200: '40C4FF',
        lightbluea400: '00B0FF',
        lightbluea700: '0091EA',
        cyan50: 'E0F7FA',
        cyan100: 'B2EBF2',
        cyan200: '80DEEA',
        cyan300: '4DD0E1',
        cyan400: '26C6DA',
        cyan500: '00BCD4',
        cyan600: '00ACC1',
        cyan700: '0097A7',
        cyan800: '00838F',
        cyan900: '006064',
        cyana100: '84FFFF',
        cyana200: '18FFFF',
        cyana400: '00E5FF',
        cyana700: '00B8D4',
        teal50: 'E0F2F1',
        teal100: 'B2DFDB',
        teal200: '80CBC4',
        teal300: '4DB6AC',
        teal400: '26A69A',
        teal500: '009688',
        teal600: '00897B',
        teal700: '00796B',
        teal800: '00695C',
        teal900: '004D40',
        teala100: 'A7FFEB',
        teala200: '64FFDA',
        teala400: '1DE9B6',
        teala700: '00BFA5',
        green50: 'E8F5E9',
        green100: 'C8E6C9',
        green200: 'A5D6A7',
        green300: '81C784',
        green400: '66BB6A',
        green500: '4CAF50',
        green600: '43A047',
        green700: '388E3C',
        green800: '2E7D32',
        green900: '1B5E20',
        greena100: 'B9F6CA',
        greena200: '69F0AE',
        greena400: '00E676',
        greena700: '00C853',
        lightgreen50: 'F1F8E9',
        lightgreen100: 'DCEDC8',
        lightgreen200: 'C5E1A5',
        lightgreen300: 'AED581',
        lightgreen400: '9CCC65',
        lightgreen500: '8BC34A',
        lightgreen600: '7CB342',
        lightgreen700: '689F38',
        lightgreen800: '558B2F',
        lightgreen900: '33691E',
        lightgreena100: 'CCFF90',
        lightgreena200: 'B2FF59',
        lightgreena400: '76FF03',
        lightgreena700: '64DD17',
        lime50: 'F9FBE7',
        lime100: 'F0F4C3',
        lime200: 'E6EE9C',
        lime300: 'DCE775',
        lime400: 'D4E157',
        lime500: 'CDDC39',
        lime600: 'C0CA33',
        lime700: 'AFB42B',
        lime800: '9E9D24',
        lime900: '827717',
        limea100: 'F4FF81',
        limea200: 'EEFF41',
        limea400: 'C6FF00',
        limea700: 'AEEA00',
        yellow50: 'FFFDE7',
        yellow100: 'FFF9C4',
        yellow200: 'FFF59D',
        yellow300: 'FFF176',
        yellow400: 'FFEE58',
        yellow500: 'FFEB3B',
        yellow600: 'FDD835',
        yellow700: 'FBC02D',
        yellow800: 'F9A825',
        yellow900: 'F57F17',
        yellowa100: 'FFFF8D',
        yellowa200: 'FFFF00',
        yellowa400: 'FFEA00',
        yellowa700: 'FFD600',
        amber50: 'FFF8E1',
        amber100: 'FFECB3',
        amber200: 'FFE082',
        amber300: 'FFD54F',
        amber400: 'FFCA28',
        amber500: 'FFC107',
        amber600: 'FFB300',
        amber700: 'FFA000',
        amber800: 'FF8F00',
        amber900: 'FF6F00',
        ambera100: 'FFE57F',
        ambera200: 'FFD740',
        ambera400: 'FFC400',
        ambera700: 'FFAB00',
        orange50: 'FFF3E0',
        orange100: 'FFE0B2',
        orange200: 'FFCC80',
        orange300: 'FFB74D',
        orange400: 'FFA726',
        orange500: 'FF9800',
        orange600: 'FB8C00',
        orange700: 'F57C00',
        orange800: 'EF6C00',
        orange900: 'E65100',
        orangea100: 'FFD180',
        orangea200: 'FFAB40',
        orangea400: 'FF9100',
        orangea700: 'FF6D00',
        deeporange50: 'FBE9E7',
        deeporange100: 'FFCCBC',
        deeporange200: 'FFAB91',
        deeporange300: 'FF8A65',
        deeporange400: 'FF7043',
        deeporange500: 'FF5722',
        deeporange600: 'F4511E',
        deeporange700: 'E64A19',
        deeporange800: 'D84315',
        deeporange900: 'BF360C',
        deeporangea100: 'FF9E80',
        deeporangea200: 'FF6E40',
        deeporangea400: 'FF3D00',
        deeporangea700: 'DD2C00',
        brown50: 'EFEBE9',
        brown100: 'D7CCC8',
        brown200: 'BCAAA4',
        brown300: 'A1887F',
        brown400: '8D6E63',
        brown500: '795548',
        brown600: '6D4C41',
        brown700: '5D4037',
        brown800: '4E342E',
        brown900: '3E2723',
        /* Material Design for Bootstrap v4 themes https://mdbootstrap.com/docs/b4/jquery/css/colors/#mdb-colors */
        'mdb-default': '2BBBAD', 'mdb-default-dark': '00695c',
        'mdb-primary': '4285F4', 'mdb-primary-dark': '0d47a1',
        'mdb-secondary': 'aa66cc', 'mdb-secondary-dark': '9933CC',
        'mdb-danger': 'ff4444', 'mdb-danger-dark': 'CC0000',
        'mdb-warning': 'ffbb33', 'mdb-warning-dark': 'FF8800',
        'mdb-success': '00C851', 'mdb-success-dark': '007E33',
        'mdb-info': '33b5e5', 'mdb-info-dark': '0099CC',
        'mdb-elegant': '2E2E2E', 'mdb-elegant-dark': '212121',
        'mdb-stylish': '4B515D', 'mdb-stylish-dark': '3E4551',
        'mdb-unique': '3F729B', 'mdb-unique-dark': '1C2331',
        'mdb-special': '37474F', 'mdb-special-dark': '263238'
    },
    errorReporting: 1,
    modifier: false,
    helper: (() => {
        document.addEventListener('keydown', e => jsPanel.modifier = e);
        document.addEventListener('keyup', () => jsPanel.modifier = false);
    })(),

    usePointerEvents(use = true) {
        if (!use) {
            this.pointerdown = 'ontouchend' in window ? ['touchstart', 'mousedown'] : ['mousedown'];
            this.pointermove = 'ontouchend' in window ? ['touchmove', 'mousemove'] : ['mousemove'];
            this.pointerup = 'ontouchend' in window ? ['touchend', 'mouseup'] : ['mouseup'];
        } else {
            this.pointerdown = 'onpointerdown' in window ? ['pointerdown'] : 'ontouchend' in window ? ['touchstart', 'mousedown'] : ['mousedown'];
            this.pointermove = 'onpointermove' in window ? ['pointermove'] : 'ontouchend' in window ? ['touchmove', 'mousemove'] : ['mousemove'];
            this.pointerup = 'onpointerup' in window ? ['pointerup'] : 'ontouchend' in window ? ['touchend', 'mouseup'] : ['mouseup'];
        }
    },

    // normalize some values passed via panel's options object
    pOcontainer(container) {
        if (container === 'window') {
            return document.body;
        } else if (typeof container === 'string') {
            let list = document.querySelectorAll(container);
            // a returned list is a NodeList
            return list.length && list.length > 0 ? list : false;
        } else if (container.nodeType === 1) {
            return container;
        } else if (container.length) {
            return container[0];
        }
        return false;
    },
    pOcontainment(arg) {
        let value = arg;
        if (typeof arg === 'function') {
            value = arg();
        }
        if (typeof value === 'number') {
            // value: 20 => value: [20, 20, 20, 20]
            return [value, value, value, value];
        } else if (Array.isArray(value)) {
            if (value.length === 1) {
                // value: [20] => value: [20, 20, 20, 20]
                return [value[0], value[0], value[0], value[0]];
            } else if (value.length === 2) {
                // value: [20, 40] => value: [20, 40, 20, 40]
                return value.concat(value);
            } else if (value.length === 3) {
                value[3] = value[1];
            }
        }
        return value; // assumed to be an array with 4 values
    },
    pOsize(panel, size) {
        let values = size || this.defaults.contentSize;
        const parent = panel.parentElement;
        if (typeof values === 'string') {
            const nums = values.trim().split(' ');
            values = {};
            values.width = nums[0];
            nums.length === 2 ? (values.height = nums[1]) : (values.height = nums[0]);
        } else {
            if (values.width && !values.height) {
                // noinspection JSSuspiciousNameCombination
                values.height = values.width;
            } else if (values.height && !values.width) {
                // noinspection JSSuspiciousNameCombination
                values.width = values.height;
            }
        }

        if (String(values.width).match(/^[\d.]+$/gi)) {
            // if number only
            values.width += 'px';
        } else if (typeof values.width === 'string' && values.width.endsWith('%')) {
            if (parent === document.body) {
                values.width = window.innerWidth * (parseFloat(values.width) / 100) + 'px';
            } else {
                const prtStyles = window.getComputedStyle(parent),
                    border = parseFloat(prtStyles.borderLeftWidth) + parseFloat(prtStyles.borderRightWidth);
                values.width = (parseFloat(prtStyles.width) - border) * (parseFloat(values.width) / 100) + 'px';
            }
        } else if (typeof values.width === 'function') {
            values.width = values.width.call(panel, panel);
            if (typeof values.width === 'number') {
                values.width += 'px';
            } else if (typeof values.width === 'string' && values.width.match(/^[\d.]+$/gi)) {
                values.width += 'px';
            }
        }

        if (String(values.height).match(/^[\d.]+$/gi)) {
            // if number only
            values.height += 'px';
        } else if (typeof values.height === 'string' && values.height.endsWith('%')) {
            if (parent === document.body) {
                values.height = window.innerHeight * (parseFloat(values.height) / 100) + 'px';
            } else {
                const prtStyles = window.getComputedStyle(parent),
                    border = parseFloat(prtStyles.borderTopWidth) + parseFloat(prtStyles.borderBottomWidth);
                values.height = (parseFloat(prtStyles.height) - border) * (parseFloat(values.height) / 100) + 'px';
            }
        } else if (typeof values.height === 'function') {
            values.height = values.height.call(panel, panel);
            if (typeof values.height === 'number') {
                values.height += 'px';
            } else if (typeof values.height === 'string' && values.height.match(/^[\d.]+$/gi)) {
                values.height += 'px';
            }
        }
        return values; // return value must be an object {width: xxx, height: xxx}
    },
    pOborder(str) {
        let result = [];
        // remove all unnecessary whitepsace
        let border = str.trim()
            .replace(/\s*\(\s*/g, '(') // remove whitespace around opening brackets
            .replace(/\s*\)/g, ')')    // remove whitespace around closing brackets
            .replace(/\s+/g, ' ')      // replace all other whitespace(s) with a single whitespace
            .split(' ');
        // replace css custom props/variables with values
        border.forEach((val, index) => {
            if(val.startsWith('--') || val.startsWith('var')) {
                border[index] = jsPanel.getCssVariableValue(val);
            }
        }); // border is now an array like ['5px', 'solid', 'red']
        // check values for type (border width, style or color) and add to result array
        border.forEach(val => {
            if (jsPanel.colorNames[val]) {
                result[2] = '#' + jsPanel.colorNames[val];
            } else if (val.match(/(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)/)) {
                result[1] = val;
            } else if (val.match(/(thin|medium|thick)|(\d*\.?\d*(cap|ch|em|ex|ic|lh|rem|rlh|vh|vw|vmax|vmin|vb|vi|px|cm|mm|Q|in|pc|pt))/)) {
                result[0] = val;
            } else {
                result[2] = val;
            }
        });
        // set default values if needed
        if (!result[0]) {result[0] = 'medium';}
        if (!result[1]) {result[1] = 'solid';}
        if (!result[2]) {result[2] = '';}
        return result;
    },
    pOheaderControls(oHdrCtrls) {
        if (typeof oHdrCtrls === 'string') {
            let setting = {},
                str = oHdrCtrls.toLowerCase(),
                sizeMatch = str.match(/xl|lg|md|sm|xs/),
                ctrlMatch = str.match(/closeonly|none/);
            if (sizeMatch) {
                setting.size = sizeMatch[0];
            }
            if (ctrlMatch) {
                setting = Object.assign({}, setting, {
                    maximize: 'remove',
                    normalize: 'remove',
                    minimize: 'remove',
                    smallify: 'remove',
                });
                if (ctrlMatch[0] === 'none') {
                    setting.close = 'remove';
                }
            }
            return Object.assign({}, this.defaults.headerControls, setting);
        } else {
            return Object.assign({}, this.defaults.headerControls, oHdrCtrls);
        }
    },
    pOtheme(optionTheme) {
        optionTheme = optionTheme.trim();
        let color,
            filling = '';
        if (optionTheme.match(/^(rgb|hsl|var)/)) {
            // for themes starting with rgb , hsl or var
            let devide = optionTheme.indexOf(')');
            color = optionTheme.slice(0, devide + 1).replace(/\s+/g, '');
            if (color.startsWith('var')) {
                color = jsPanel.getCssVariableValue(color);
            }
            filling = optionTheme.slice(devide + 1, optionTheme.length).trim();
        } else if (optionTheme.match(/^(#|\w|--)/)) {
            // for themes starting with #, [A-Za-z0-9_] or --
            let devide = optionTheme.indexOf(' ');
            if (devide > 0) {
                color = optionTheme.slice(0,devide + 1).replace(/\s+/g, '');
                filling = optionTheme.slice(devide + 1, optionTheme.length).trim();
            } else {
                color = optionTheme;
            }
            if (color.startsWith('--')) {
                color = jsPanel.getCssVariableValue(color);
            }
        }
        if (color.match(/^([\da-f]{3}|[\da-f]{6})$/gi)) {
            color = '#' + color;
        }
        if (filling.startsWith('fillcolor')) {
            let devide = filling.indexOf(' ');
            filling = filling.slice(devide + 1, filling.length).trim().replace(/\s+/g, '');
            if (filling.match(/^([\da-f]{3}|[\da-f]{6})$/gi)) {
                filling = '#' + filling;
            } else if (jsPanel.colorNames[filling]) {
                filling = '#' + jsPanel.colorNames[filling];
            } else if (filling.match(/^(--|var)/)) {
                filling = jsPanel.getCssVariableValue(filling);
            } else {
                filling = '#fff';
            }
        }
        return {color: color, colors: false, filling: filling};
    },

    // color methods ---------------
    color(val) {
        let color = val.toLowerCase(),
            r,
            g,
            b,
            h,
            s,
            l,
            match,
            channels,
            hsl,
            result = {};
        const hexPattern = /^#?([\da-f]{3}|[\da-f]{6})$/gi, // matches "#123" or "#f05a78" with or without "#"
            RGBAPattern = /^rgba?\((\d{1,3}),(\d{1,3}),(\d{1,3}),?(0|1|0\.\d{1,2}|\.\d{1,2})?\)$/gi, // matches rgb/rgba color values, whitespace allowed
            HSLAPattern = /^hsla?\((\d{1,3}),(\d{1,3}%),(\d{1,3}%),?(0|1|0\.\d{1,2}|\.\d{1,2})?\)$/gi,
            namedColors = this.colorNames;

        // change named color to corresponding hex value
        if (namedColors[color]) {
            color = namedColors[color];
        }

        // check val for hex color
        if (color.match(hexPattern) !== null) {
            // '#' remove
            color = color.replace('#', '');
            // color has either 3 or 6 characters
            if (color.length % 2 === 1) {
                // color has 3 char -> convert to 6 char
                r = color.slice(0,1).repeat(2);
                g = color.slice(1,2).repeat(2);
                b = color.slice(2,3).repeat(2);

                result.rgb = {
                    r: parseInt(r, 16),
                    g: parseInt(g, 16),
                    b: parseInt(b, 16),
                };

                result.hex = `#${r}${g}${b}`;
            } else {
                // color has 6 char
                result.rgb = {
                    r: parseInt(color.slice(0, 2), 16),
                    g: parseInt(color.slice(2, 4), 16),
                    b: parseInt(color.slice(4, 6), 16)
                };

                result.hex = `#${color}`;
            }

            hsl = this.rgbToHsl(result.rgb.r, result.rgb.g, result.rgb.b);
            result.hsl = hsl;
            result.rgb.css = `rgb(${result.rgb.r},${result.rgb.g},${result.rgb.b})`;
        }
        // check val for rgb/rgba color
        else if (color.match(RGBAPattern)) {
            match = RGBAPattern.exec(color);
            result.rgb = { css: color, r: match[1], g: match[2], b: match[3] };
            result.hex = this.rgbToHex(match[1], match[2], match[3]);
            hsl = this.rgbToHsl(match[1], match[2], match[3]);
            result.hsl = hsl;
        }
        // check val for hsl/hsla color
        else if (color.match(HSLAPattern)) {
            match = HSLAPattern.exec(color);

            h = match[1] / 360;
            s = match[2].slice(0, match[2].length - 1) / 100;
            l = match[3].slice(0, match[3].length - 1) / 100;

            channels = this.hslToRgb(h, s, l);

            result.rgb = {
                css: `rgb(${channels[0]},${channels[1]},${channels[2]})`,
                r: channels[0],
                g: channels[1],
                b: channels[2],
            };
            result.hex = this.rgbToHex(result.rgb.r, result.rgb.g, result.rgb.b);
            result.hsl = {
                css: `hsl(${match[1]},${match[2]},${match[3]})`,
                h: match[1],
                s: match[2],
                l: match[3],
            };
        }

        // or return #f5f5f5
        else {
            result.hex = '#f5f5f5';
            result.rgb = { css: 'rgb(245,245,245)', r: 245, g: 245, b: 245 };
            result.hsl = { css: 'hsl(0,0%,96%)', h: 0, s: '0%', l: '96%' };
        }

        return result;
    },
    calcColors(primaryColor) {
        const threshold = this.colorBrightnessThreshold,
            primeColor = this.color(primaryColor),
            filledlightColor = this.lighten(primaryColor, this.colorFilledLight),
            filledColor = this.darken(primaryColor, this.colorFilled),
            fontColorForPrimary = this.perceivedBrightness(primaryColor) <= threshold ? '#ffffff' : '#000000',
            fontColorFilledlight = this.perceivedBrightness(filledlightColor) <= threshold ? '#ffffff' : '#000000',
            fontColorFilled = this.perceivedBrightness(filledColor) <= threshold ? '#ffffff' : '#000000',
            filleddarkColor = this.lighten(primaryColor, this.colorFilledDark),
            fontColorFilleddark = this.perceivedBrightness(filleddarkColor) <= threshold ? '#ffffff' : '#000000';
        return [
            primeColor.hsl.css,
            filledlightColor,
            filledColor,
            fontColorForPrimary,
            fontColorFilledlight,
            fontColorFilled,
            filleddarkColor,
            fontColorFilleddark,
        ];
    },
    darken(val, amount) {
        // amount is value between 0 and 1
        const hsl = this.color(val).hsl,
            l = parseFloat(hsl.l),
            lnew = Math.round(l - l * amount) + '%';
        return `hsl(${hsl.h},${hsl.s},${lnew})`;
    },
    lighten(val, amount) {
        // amount is value between 0 and 1
        const hsl = this.color(val).hsl,
            l = parseFloat(hsl.l),
            lnew = Math.round(l + (100 - l) * amount) + '%';
        return `hsl(${hsl.h},${hsl.s},${lnew})`;
    },
    hslToRgb(h, s, l) {
        // h, s and l must be values between 0 and 1
        let r, g, b;
        if (s === 0) {
            r = g = b = l; // achromatic
        } else {
            let hue2rgb = (p, q, t) => {
                if (t < 0) {
                    t += 1;
                }
                if (t > 1) {
                    t -= 1;
                }
                if (t < 1 / 6) {
                    return p + (q - p) * 6 * t;
                }
                if (t < 1 / 2) {
                    return q;
                }
                if (t < 2 / 3) {
                    return p + (q - p) * (2 / 3 - t) * 6;
                }
                return p;
            };
            let q = l < 0.5 ? l * (1 + s) : l + s - l * s,
                p = 2 * l - q;
            r = hue2rgb(p, q, h + 1 / 3);
            g = hue2rgb(p, q, h);
            b = hue2rgb(p, q, h - 1 / 3);
        }
        return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
    },
    rgbToHsl(r, g, b) {
        r /= 255;
        g /= 255;
        b /= 255;
        let max = Math.max(r, g, b),
            min = Math.min(r, g, b),
            h,
            s,
            l = (max + min) / 2;
        if (max === min) {
            h = s = 0; // achromatic
        } else {
            let d = max - min;
            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);

            switch (max) {
                case r:
                    h = (g - b) / d + (g < b ? 6 : 0);
                    break;
                case g:
                    h = (b - r) / d + 2;
                    break;
                case b:
                    h = (r - g) / d + 4;
                    break;
            }
            h /= 6;
        }
        //return [ h, s, l ];
        h = Math.round(h * 360);
        s = Math.round(s * 100) + '%';
        l = Math.round(l * 100) + '%';
        return { css: 'hsl(' + h + ',' + s + ',' + l + ')', h: h, s: s, l: l };
    },
    rgbToHex(r, g, b) {
        let red = Number(r).toString(16),
            green = Number(g).toString(16),
            blue = Number(b).toString(16);
        if (red.length === 1) {
            red = `0${red}`;
        }
        if (green.length === 1) {
            green = `0${green}`;
        }
        if (blue.length === 1) {
            blue = `0${blue}`;
        }
        return `#${red}${green}${blue}`;
    },
    perceivedBrightness(val) {
        const rgb = this.color(val).rgb;
        // return value is in the range 0 - 1 and input rgb values must also be in the range 0 - 1
        // https://www.w3.org/TR/WCAG20-TECHS/G18.html
        return (rgb.r / 255) * 0.2126 + (rgb.g / 255) * 0.7152 + (rgb.b / 255) * 0.0722;
    },

    // positioning methods ---------------
    pOposition(positionshorthand) {
        let result = {};
        // remove leading and trailing whitespace and split position shorthand string into array
        let pos = positionshorthand.trim().split(/\s+/);

        // find autoposition value and assign to result, must be the first item to find and remove
        let auto = pos.filter(item => item.match(/^(down|right|up|left)$/i));
        if (auto.length) {
            result.autoposition = auto[0];
            pos.splice(pos.indexOf(auto[0]), 1);
        }

        // find my and at values and assign to result
        let my_at = pos.filter(item => item.match(/^(left-|right-)(top|center|bottom)$|(^center-)(top|bottom)$|(^center$)/i));
        if (my_at.length) {
            result.my = my_at[0];
            result.at = my_at[1] || my_at[0];
            pos.splice(pos.indexOf(my_at[0]), 1);
            if (my_at[1]) {
                pos.splice(pos.indexOf(my_at[1]), 1);
            }
        } else {
            result.my = 'center';
            result.at = 'center';
        }

        // find offset and assign to result
        let offsets = pos.filter(item => item.match(/^[+-]?\d*\.?\d+[a-z%]*$/i));
        if (offsets.length) {
            result.offsetX = offsets[0].match(/^[+-]?\d*\.?\d+$/i) ? `${offsets[0]}px` : offsets[0];
            if (offsets[1]) {
                result.offsetY = offsets[1].match(/^[+-]?\d*\.?\d+$/i) ? `${offsets[1]}px` : offsets[1];
            } else {
                // noinspection JSSuspiciousNameCombination
                result.offsetY = result.offsetX;
            }
            pos.splice(pos.indexOf(offsets[0]), 1);
            if (offsets[1]) {
                pos.splice(pos.indexOf(offsets[1]), 1);
            }
        }

        // last to find and assign is of value and must be all the rest (if there is a rest)
        if (pos.length) {
            result.of = pos.join(' ');
        }

        return result;
    },
    position(panel, position) {
        // @panel:     selector string | Element | jQuery object
        //                - usually the jsPanel to position
        // @position:  object
        //                - positioning configuration
        //                - if panel config uses a position shorthand string it must be converted to object before it's passed to this function

        // if @position is not set return panel
        if (!position) {
            panel.style.opacity = 1;
            return panel;
        }

        // merge position defaults with @position
        if (typeof position === 'string') {
            position = Object.assign({}, this.defaults.position, this.pOposition(position));
        } else {
            position = Object.assign({}, this.defaults.position, position);
        }

        // process parameter functions for 'my', 'at', 'of'
        // 'offsetX', 'offsetY', 'minLeft', 'maxLeft', 'minTop', 'maxTop' are processed when params are applied
        ['my', 'at', 'of'].forEach(item => {
            if (typeof position[item] === 'function') {
                position[item] = position[item].call(panel, panel);
            }
        });

        // panel uses option.container: 'window' position is always fixed
        if (panel.options.container === 'window') {
            panel.style.position = 'fixed';
        }

        // normalize param @panel to ensure it's an Element object
        if (typeof panel === 'string') {
            panel = document.querySelector(panel);
        } else if (Object.getPrototypeOf(panel).jquery) {
            panel = panel[0];
        } // else panel is assumed to be element object

        // container is either 'window' or the panel's parent element
        const container = panel.options.container === 'window' ? 'window' : panel.parentElement;

        // get base values in order to calculate position deltas
        // since getBoundingClientRect() calculates values relative to the viewport the parentElement of panel/elmtToPositionAgainst is irrelevant
        const panelRect = panel.getBoundingClientRect(),
            containerDomRect = panel.parentElement.getBoundingClientRect(),
            containerRect =
                container === 'window'
                    ? {
                          left: 0,
                          top: 0,
                          width: document.documentElement.clientWidth,
                          height: window.innerHeight,
                      } // fake window.getBoundingClientRect() return value
                    : //: panel.parentElement.getBoundingClientRect(); // using 'container' instead of 'panel.parentElement' produces an error
                      // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#Notes
                      // due to the infos from above link IE and EDGE (old version not based on Chromium) report an error in strict mode -> line of code above replaced with line below
                      {
                          width: containerDomRect.width,
                          height: containerDomRect.height,
                          left: containerDomRect.left,
                          top: containerDomRect.top,
                      };

        // calculate scale factors, needed for correct positioning if container is scaled - transform: scale()
        // window is never scaled --> scale factors default to 1
        const scaleFactor =
            container === 'window'
                ? {x: 1, y: 1}
                : {
                      x: containerRect.width / container.offsetWidth,
                      y: containerRect.height / container.offsetHeight,
                  };

        // get and apply border width values of container - needed for positioning corrections due to positioning with %-values
        const containerStyle =
            container === 'window'
                ? {
                      borderTopWidth: '0px',
                      borderRightWidth: '0px',
                      borderBottomWidth: '0px',
                      borderLeftWidth: '0px',
                  } // fake getComputedStyle(window) return value
                : window.getComputedStyle(container);

        containerRect.width -= (parseFloat(containerStyle.borderLeftWidth) + parseFloat(containerStyle.borderRightWidth)) * scaleFactor.x;
        containerRect.height -= (parseFloat(containerStyle.borderTopWidth) + parseFloat(containerStyle.borderBottomWidth)) * scaleFactor.y;

        // calculate @position.of rect if @position.of is set
        let positionOfRect;
        if (!position.of) {
            positionOfRect = containerRect;
        } else {
            if (typeof position.of === 'string') {
                positionOfRect =
                    position.of === 'window'
                        ? {
                              borderTopWidth: '0px',
                              borderRightWidth: '0px',
                              borderBottomWidth: '0px',
                              borderLeftWidth: '0px',
                          } // fake getComputedStyle(window) return value
                        : document.querySelector(position.of).getBoundingClientRect();
            } else if (Object.getPrototypeOf(position.of).jquery) {
                positionOfRect = position.of[0].getBoundingClientRect();
            } else {
                positionOfRect = position.of.getBoundingClientRect();
            }
        }

        // check for scrollbar width values
        let scrollbarwidthsWindow = this.getScrollbarWidth(document.body),
            scrollbarwidthsContainer = this.getScrollbarWidth(panel.parentElement);

        // calc css left for @panel in regard of @position.my and @position.at
        let left = '0px';
        if (position.my.startsWith('left-')) {
            if (position.at.startsWith('left-')) {
                if (position.of) {
                    left = positionOfRect.left - containerRect.left - parseFloat(containerStyle.borderLeftWidth) + 'px';
                } else {
                    left = '0px';
                }
            } else if (position.at.startsWith('center')) {
                if (position.of) {
                    left = positionOfRect.left - containerRect.left - parseFloat(containerStyle.borderLeftWidth) + positionOfRect.width / 2 + 'px';
                } else {
                    left = containerRect.width / 2 + 'px';
                }
            } else if (position.at.startsWith('right-')) {
                if (position.of) {
                    left = positionOfRect.left - containerRect.left - parseFloat(containerStyle.borderLeftWidth) + positionOfRect.width + 'px';
                } else {
                    left = containerRect.width + 'px';
                }
            }
        } else if (position.my.startsWith('center')) {
            if (position.at.startsWith('left-')) {
                if (position.of) {
                    left = positionOfRect.left - containerRect.left - parseFloat(containerStyle.borderLeftWidth) - panelRect.width / 2 + 'px';
                } else {
                    left = -panelRect.width / 2 + 'px';
                }
            } else if (position.at.startsWith('center')) {
                if (position.of) {
                    left =
                        positionOfRect.left -
                        containerRect.left -
                        parseFloat(containerStyle.borderLeftWidth) -
                        (panelRect.width - positionOfRect.width) / 2 +
                        'px';
                } else {
                    left = containerRect.width / 2 - panelRect.width / 2 + 'px';
                }
            } else if (position.at.startsWith('right-')) {
                if (position.of) {
                    left =
                        positionOfRect.left -
                        containerRect.left -
                        parseFloat(containerStyle.borderLeftWidth) +
                        (positionOfRect.width - panelRect.width / 2) +
                        'px';
                } else {
                    left = containerRect.width - panelRect.width / 2 + 'px';
                }
            }
        } else if (position.my.startsWith('right-')) {
            if (position.at.startsWith('left-')) {
                if (position.of) {
                    left = positionOfRect.left - containerRect.left - parseFloat(containerStyle.borderLeftWidth) - panelRect.width + 'px';
                } else {
                    left = -panelRect.width + 'px';
                }
            } else if (position.at.startsWith('center')) {
                if (position.of) {
                    left =
                        positionOfRect.left -
                        containerRect.left -
                        parseFloat(containerStyle.borderLeftWidth) -
                        panelRect.width +
                        positionOfRect.width / 2 +
                        'px';
                } else {
                    left = containerRect.width / 2 - panelRect.width + 'px';
                }
            } else if (position.at.startsWith('right-')) {
                if (position.of) {
                    left =
                        positionOfRect.left -
                        containerRect.left -
                        parseFloat(containerStyle.borderLeftWidth) +
                        positionOfRect.width -
                        panelRect.width +
                        'px';
                } else {
                    left = containerRect.width - panelRect.width + 'px';
                }
                // correction for vertical scrollbar only needed for panels using my: 'right-*' together with at: 'right-*'
                if (container !== 'window') {
                    left = parseFloat(left) - scrollbarwidthsContainer.y + 'px';
                }
            }
        }

        // calc css top for @panel in regard of @position.my and @position.at
        let top = '0px';
        if (position.my.endsWith('-top')) {
            if (position.at.endsWith('-top')) {
                if (position.of) {
                    top = positionOfRect.top - containerRect.top - parseFloat(containerStyle.borderTopWidth) + 'px';
                } else {
                    top = '0px';
                }
            } else if (position.at.endsWith('center')) {
                if (position.of) {
                    top = positionOfRect.top - containerRect.top - parseFloat(containerStyle.borderTopWidth) + positionOfRect.height / 2 + 'px';
                } else {
                    top = containerRect.height / 2 + 'px';
                }
            } else if (position.at.endsWith('-bottom')) {
                if (position.of) {
                    top = positionOfRect.top - containerRect.top - parseFloat(containerStyle.borderTopWidth) + positionOfRect.height + 'px';
                } else {
                    top = containerRect.height + 'px';
                }
            }
        } else if (position.my.endsWith('center')) {
            if (position.at.endsWith('-top')) {
                if (position.of) {
                    top = positionOfRect.top - containerRect.top - parseFloat(containerStyle.borderTopWidth) - panelRect.height / 2 + 'px';
                } else {
                    top = -panelRect.height / 2 + 'px';
                }
            } else if (position.at.endsWith('center')) {
                if (position.of) {
                    top =
                        positionOfRect.top -
                        containerRect.top -
                        parseFloat(containerStyle.borderTopWidth) -
                        panelRect.height / 2 +
                        positionOfRect.height / 2 +
                        'px';
                } else {
                    top = containerRect.height / 2 - panelRect.height / 2 + 'px';
                }
            } else if (position.at.endsWith('-bottom')) {
                if (position.of) {
                    top =
                        positionOfRect.top -
                        containerRect.top -
                        parseFloat(containerStyle.borderTopWidth) -
                        panelRect.height / 2 +
                        positionOfRect.height +
                        'px';
                } else {
                    top = containerRect.height - panelRect.height / 2 + 'px';
                }
            }
        } else if (position.my.endsWith('-bottom')) {
            if (position.at.endsWith('-top')) {
                if (position.of) {
                    top = positionOfRect.top - containerRect.top - parseFloat(containerStyle.borderTopWidth) - panelRect.height + 'px';
                } else {
                    top = -panelRect.height + 'px';
                }
            } else if (position.at.endsWith('center')) {
                if (position.of) {
                    top =
                        positionOfRect.top -
                        containerRect.top -
                        parseFloat(containerStyle.borderTopWidth) -
                        panelRect.height +
                        positionOfRect.height / 2 +
                        'px';
                } else {
                    top = containerRect.height / 2 - panelRect.height + 'px';
                }
            } else if (position.at.endsWith('-bottom')) {
                if (position.of) {
                    top =
                        positionOfRect.top -
                        containerRect.top -
                        parseFloat(containerStyle.borderTopWidth) -
                        panelRect.height +
                        positionOfRect.height +
                        'px';
                } else {
                    top = containerRect.height - panelRect.height + 'px';
                }
                // correction for horizontal scrollbar only needed for panels using my: '*-bottom' together with at: '*-bottom'
                if (container !== 'window') {
                    top = parseFloat(top) - scrollbarwidthsContainer.x + 'px';
                } else {
                    top = parseFloat(top) - scrollbarwidthsWindow.x + 'px';
                }
            }
        }
        panel.style.left = scaleFactor.x === 1 ? left : parseFloat(left) / scaleFactor.x + 'px';
        panel.style.top = scaleFactor.y === 1 ? top : parseFloat(top) / scaleFactor.y + 'px';
        // at this point panels are correctly positioned according to my/at values

        let panelStyle = getComputedStyle(panel);
        // eslint-disable-next-line no-unused-vars
        let pos = { left: panelStyle.left, top: panelStyle.top };
        //console.log('pos after applying my/at/of:', pos);

        // apply autoposition only if ...
        if (
            position.autoposition &&
            position.my === position.at &&
            ['left-top', 'center-top', 'right-top', 'left-bottom', 'center-bottom', 'right-bottom'].indexOf(position.my) >= 0
        ) {
            pos = this.applyPositionAutopos(panel, pos, position);
            //console.log('let pos after applying autoposition:', pos);
        }

        // apply position.offsetX and position.offsetY
        if (position.offsetX || position.offsetY) {
            pos = this.applyPositionOffset(panel, pos, position);
            //console.log('pos after applying offsets:', pos);
        }

        // calculate and apply position.minLeft, position.minTop, position.maxLeft and position.maxTop
        if (position.minLeft || position.minTop || position.maxLeft || position.maxTop) {
            pos = this.applyPositionMinMax(panel, pos, position);
            //console.log('pos after applying minLeft, maxLeft, maxTop, minTop:', pos);
        }

        // apply position.modify
        // must be function returning an object with keys left/top, each with valid css length value
        if (position.modify) {
            // eslint-disable-next-line no-unused-vars
            pos = this.applyPositionModify(panel, pos, position);
            //console.log('pos after applying modify():', pos);
        }

        typeof panel.options.opacity === 'number' ? (panel.style.opacity = panel.options.opacity) : (panel.style.opacity = 1);

        return panel;
    },
    applyPositionAutopos(panel, pos, position) {
        // add class with position and autoposition direction
        const newClass = `${position.my}-${position.autoposition.toLowerCase()}`;
        panel.classList.add(newClass);

        // get all panels with same class
        const newClassAll = Array.prototype.slice.call(document.querySelectorAll(`.${newClass}`)),
            ownIndex = newClassAll.indexOf(panel);

        // if more than 1 position new panel
        if (newClassAll.length > 1) {
            switch (position.autoposition) {
                case 'down':
                    // collect heights of all elmts to calc new top position
                    newClassAll.forEach((item, index) => {
                        if (index > 0 && index <= ownIndex) {
                            pos.top = parseFloat(pos.top) + newClassAll[--index].getBoundingClientRect().height + jsPanel.autopositionSpacing + 'px';
                        }
                    });
                    break;
                case 'up':
                    newClassAll.forEach((item, index) => {
                        if (index > 0 && index <= ownIndex) {
                            pos.top = parseFloat(pos.top) - newClassAll[--index].getBoundingClientRect().height - jsPanel.autopositionSpacing + 'px';
                        }
                    });
                    break;
                case 'right':
                    // collect widths of all elmts to calc new left position
                    newClassAll.forEach((item, index) => {
                        if (index > 0 && index <= ownIndex) {
                            pos.left = parseFloat(pos.left) + newClassAll[--index].getBoundingClientRect().width + jsPanel.autopositionSpacing + 'px';
                        }
                    });
                    break;
                case 'left':
                    newClassAll.forEach((item, index) => {
                        if (index > 0 && index <= ownIndex) {
                            pos.left = parseFloat(pos.left) - newClassAll[--index].getBoundingClientRect().width - jsPanel.autopositionSpacing + 'px';
                        }
                    });
                    break;
            }
            panel.style.left = pos.left;
            panel.style.top = pos.top;
        }
        return { left: pos.left, top: pos.top };
    },
    applyPositionOffset(panel, pos, position) {
        ['offsetX', 'offsetY'].forEach(offset => {
            if (position[offset]) {
                if (typeof position[offset] === 'function') {
                    position[offset] = position[offset].call(pos, pos, position);
                }
                if (isNaN(position[offset]) === false) {
                    // if an offset's value type is integer it's interpreted as pixel value
                    position[offset] = `${position[offset]}px`;
                } // else it's assumed offsets are strings with valid css length values
            } else {
                position[offset] = '0px';
            }
        });
        panel.style.left = `calc(${panel.style.left} + ${position.offsetX})`; // offsetX
        panel.style.top = `calc(${panel.style.top} + ${position.offsetY})`; // offsetY
        const panelStyle = getComputedStyle(panel);
        return { left: panelStyle.left, top: panelStyle.top };
    },
    applyPositionMinMax(panel, pos, position) {
        ['minLeft', 'minTop', 'maxLeft', 'maxTop'].forEach(val => {
            if (position[val]) {
                if (typeof position[val] === 'function') {
                    position[val] = position[val].call(pos, pos, position);
                }
                if (Number.isInteger(position[val]) || position[val].match(/^\d+$/)) {
                    // if  val type is integer it's interpreted as pixel value
                    position[val] = `${position[val]}px`;
                } // else it's assumed val is string with valid css length value
            }
        });
        // process minLeft
        if (position.minLeft) {
            // apply minLeft value in order to compare with previous left (pos.left)
            panel.style.left = position.minLeft;
            // now get computed css left
            let left = getComputedStyle(panel).left; // returns string with pixel value
            // now compare current left (minLeft) with pos.left
            if (parseFloat(left) < parseFloat(pos.left)) {
                // if minLeft is less than pos.left return to pos.left
                panel.style.left = pos.left;
            } else {
                // if minLeft is greater than pos.left keep minLeft and reset pos.left to new value
                pos.left = left;
            }
        }
        // process minTop
        if (position.minTop) {
            panel.style.top = position.minTop;
            let top = getComputedStyle(panel).top;
            if (parseFloat(top) < parseFloat(pos.top)) {
                panel.style.top = pos.top;
            } else {
                pos.top = top;
            }
        }
        // process maxLeft
        if (position.maxLeft) {
            panel.style.left = position.maxLeft;
            let left = getComputedStyle(panel).left;
            if (parseFloat(left) > parseFloat(pos.left)) {
                panel.style.left = pos.left;
            } else {
                pos.left = left;
            }
        }
        // process maxTop
        if (position.maxTop) {
            panel.style.top = position.maxTop;
            let top = getComputedStyle(panel).top;
            if (parseFloat(top) > parseFloat(pos.top)) {
                panel.style.top = pos.top;
            } else {
                pos.top = top;
            }
        }
        const panelStyle = getComputedStyle(panel);
        return { left: panelStyle.left, top: panelStyle.top };
    },
    applyPositionModify(panel, pos, position) {
        if (position.modify && typeof position.modify === 'function') {
            const modifiedPosition = position.modify.call(pos, pos, position);
            panel.style.left =
                Number.isInteger(modifiedPosition.left) || modifiedPosition.left.match(/^\d+$/)
                    ? `${modifiedPosition.left}px`
                    : modifiedPosition.left;
            panel.style.top =
                Number.isInteger(modifiedPosition.top) || modifiedPosition.top.match(/^\d+$/) ? `${modifiedPosition.top}px` : modifiedPosition.top;
        }
        const panelStyle = getComputedStyle(panel);
        return { left: panelStyle.left, top: panelStyle.top };
    },
    autopositionRemaining(panel) {
        let autoPos,
            parent = panel.options.container;
        [
            'left-top-down',
            'left-top-right',
            'center-top-down',
            'right-top-down',
            'right-top-left',
            'left-bottom-up',
            'left-bottom-right',
            'center-bottom-up',
            'right-bottom-up',
            'right-bottom-left',
        ].forEach(item => {
            if (panel.classList.contains(item)) {
                autoPos = item;
            }
        });
        if (autoPos) {
            const box = parent === 'window' ? document.body : typeof parent === 'string' ? document.querySelector(parent) : parent;
            box.querySelectorAll(`.${autoPos}`).forEach(item => item.reposition());
        }
    },

    // theming methods -------------------------
    getThemeDetails(th) {
        const theme = this.pOtheme(th);
        if (theme.color.startsWith('bootstrap-')) {
            // works with bootstrap 3 and 4
            let index = theme.color.indexOf('-'),
                btn = document.createElement('button');
            btn.className = 'btn btn' + theme.color.slice(index);
            document.body.appendChild(btn);
            theme.color = getComputedStyle(btn).backgroundColor.replace(/\s+/gi, '');
            document.body.removeChild(btn);
            // noinspection JSUnusedAssignment
            btn = undefined;
        }
        theme.colors = this.calcColors(theme.color);
        return theme;
    },
    clearTheme(panel, cb) {
        panel.content.classList.remove('jsPanel-content-filled', 'jsPanel-content-filledlight');
        panel.header.classList.remove('jsPanel-hdr-light');
        panel.header.classList.remove('jsPanel-hdr-dark');
        panel.style.backgroundColor = '';
        this.setStyles(panel.headertoolbar, {
            boxShadow: '',
            width: '',
            marginLeft: '',
            borderTopColor: 'transparent',
        });
        this.setStyles(panel.content, {
            background: '',
            borderTopColor: 'transparent',
        });
        panel.header.style.background = '';
        Array.prototype.slice
            .call(panel.controlbar.querySelectorAll('.jsPanel-icon'))
            .concat([panel.headerlogo, panel.headertitle, panel.headertoolbar, panel.content])
            .forEach(item => item.style.color = '');
        if (cb) {
            cb.call(panel, panel);
        }
        return panel;
    },
    applyColorTheme(panel, themeDetails) {
        panel.style.backgroundColor = themeDetails.colors[0];
        panel.header.style.backgroundColor = themeDetails.colors[0];
        panel.header.style.color = themeDetails.colors[3];
        ['.jsPanel-headerlogo', '.jsPanel-title', '.jsPanel-hdr-toolbar'].forEach(item => panel.querySelector(item).style.color = themeDetails.colors[3]);
        panel.querySelectorAll('.jsPanel-controlbar .jsPanel-btn').forEach(item => item.style.color = themeDetails.colors[3]);
        // apply border to content only themes 'filled'
        if (typeof panel.options.theme === 'string' && themeDetails.filling === 'filled') {
            panel.content.style.borderTop =
                themeDetails.colors[3] === '#000000' ? '1px solid rgba(0,0,0,0.15)' : '1px solid rgba(255,255,255,0.15)';
        }
        if (themeDetails.colors[3] === '#000000') {
            panel.header.classList.add('jsPanel-hdr-light');
        } else {
            panel.header.classList.add('jsPanel-hdr-dark');
        }
        if (themeDetails.filling) {
            switch (themeDetails.filling) {
                case 'filled':
                    this.setStyles(panel.content, {
                        backgroundColor: themeDetails.colors[2],
                        color: themeDetails.colors[3],
                    });
                    break;
                case 'filledlight':
                    panel.content.style.backgroundColor = themeDetails.colors[1];
                    break;
                case 'filleddark':
                    this.setStyles(panel.content, {
                        backgroundColor: themeDetails.colors[6],
                        color: themeDetails.colors[7],
                    });
                    break;
                default:
                    panel.content.style.backgroundColor = themeDetails.filling;
                    panel.content.style.color =
                        this.perceivedBrightness(themeDetails.filling) <= this.colorBrightnessThreshold ? '#fff' : '#000';
            }
        }
        return panel;
    },
    applyCustomTheme(panel, theme) {
        let defaults = {
                bgPanel: '#ffffff',
                bgContent: '#ffffff',
                bgFooter: '#f5f5f5',
                colorHeader: '#000000',
                colorContent: '#000000',
                colorFooter: '#000000',
                border: undefined,
                borderRadius: undefined
            },
            passedTheme = typeof theme === 'object' ? Object.assign(defaults, theme) : defaults;
        let bgPanel = passedTheme.bgPanel,
            bgContent = passedTheme.bgContent,
            colorHeader = passedTheme.colorHeader,
            colorContent = passedTheme.colorContent,
            bgFooter = passedTheme.bgFooter,
            colorFooter = passedTheme.colorFooter;
        // set background panel/header
        if (this.colorNames[bgPanel]) {
            panel.style.background = '#' + this.colorNames[bgPanel];
        } else {
            panel.style.background = this.getCssVariableValue(bgPanel);
            // getCssVariableValue(bgPanel) returns bgPanel unchanged if value does not start with either '--' or 'var'
        }
        // set font color header
        if (this.colorNames[colorHeader]) {
            colorHeader = '#' + this.colorNames[colorHeader];
        }
        ['.jsPanel-headerlogo', '.jsPanel-title', '.jsPanel-hdr-toolbar'].forEach(item => panel.querySelector(item).style.color = this.getCssVariableValue(colorHeader));
        panel.querySelectorAll('.jsPanel-controlbar .jsPanel-btn').forEach(item => item.style.color = this.getCssVariableValue(colorHeader));
        // set content background
        if (this.colorNames[bgContent]) {
            panel.content.style.background = '#' + this.colorNames[bgContent];
        } else {
            panel.content.style.background = this.getCssVariableValue(bgContent);
            // getCssVariableValue(bgContent) returns bgContent unchanged if value does not start with either '--' or 'var'
        }
        // set content font color
        this.colorNames[colorContent]
            ? (panel.content.style.color = '#' + this.colorNames[colorContent])
            : (panel.content.style.color = this.getCssVariableValue(colorContent)); // getCssVariableValue(val) returns val unchanged if val does not start with either '--' or 'var'
        // set border-top for header toolbar and add header class
        const pbPanel = this.perceivedBrightness(colorHeader);
        if (pbPanel > this.colorBrightnessThreshold) {
            panel.header.classList.add('jsPanel-hdr-dark');
        } else {
            panel.header.classList.add('jsPanel-hdr-light');
        }
        // set border-top for content
        const pbContent = this.perceivedBrightness(colorContent);
        pbContent > this.colorBrightnessThreshold
            ? (panel.content.style.borderTop = '1px solid rgba(255,255,255,0.15)')
            : (panel.content.style.borderTop = '1px solid rgba(0,0,0,0.15)');
        // set footer background
        if (this.colorNames[bgFooter]) {
            panel.footer.style.background = '#' + this.colorNames[bgFooter];
        } else {
            panel.footer.style.background = this.getCssVariableValue(bgFooter);
            // getCssVariableValue(bgFooter) returns bgFooter unchanged if value does not start with either '--' or 'var'
        }
        // set footer font color
        this.colorNames[colorFooter]
            ? (panel.footer.style.color = '#' + this.colorNames[colorFooter])
            : (panel.footer.style.color = this.getCssVariableValue(colorFooter));
        // set panel border (option.border does not work for themes using an object)
        if (passedTheme.border) {
            panel.setBorder(passedTheme.border);
        }
        if (passedTheme.borderRadius) {
            panel.options.borderRadius = undefined;
            panel.setBorderRadius(passedTheme.borderRadius);
        }
        return panel;
    },

    // methods dealing with css and html -------
    getCssVariableValue(str) {
        // str may be something like '--my-color' or 'var(--my-color)'
        // if str does not start with '--' or 'var' str is returned unchanged
        // str = str.replace(/\s+/g, ''); do not uncomment this line !!
        if (str.startsWith('--')) {
            return getComputedStyle(document.documentElement).getPropertyValue(str).replace(/\s+/g, '');
        } else if (str.startsWith('var')) {
            let v = str.slice(str.indexOf('(')+1, str.indexOf(')'));
            return getComputedStyle(document.documentElement).getPropertyValue(v).replace(/\s+/g, '');
        }
        return str;
    },
    getScrollbarWidth(elmt = document.body) {
        if (elmt === document.body) {
            return {
                y: window.innerWidth - document.documentElement.clientWidth,
                x: window.innerHeight - document.documentElement.clientHeight,
            };
        } else {
            let styles = getComputedStyle(elmt);
            return {
                y: elmt.offsetWidth - elmt.clientWidth - parseFloat(styles.borderRightWidth) - parseFloat(styles.borderLeftWidth),
                x: elmt.offsetHeight - elmt.clientHeight - parseFloat(styles.borderBottomWidth) - parseFloat(styles.borderTopWidth),
            };
        }
    },
    remClass(elmt, classnames) {
        classnames
            .trim()
            .split(/\s+/)
            .forEach(item => elmt.classList.remove(item));
        return elmt;
    },
    setClass(elmt, classnames) {
        classnames
            .trim()
            .split(/\s+/)
            .forEach(item => elmt.classList.add(item));
        return elmt;
    },
    setStyles(elmt, stylesobject) {
        for (const [prop, value] of Object.entries(stylesobject)) {
            elmt.style[prop] = typeof value === 'string' ? jsPanel.getCssVariableValue(value) : value;
        }
        return elmt;
    },
    setStyle(elmt, stylesobject) {
        return this.setStyles.call(elmt, elmt, stylesobject);
    }, // alias for setStyles()
    strToHtml(str) {
        // TODO: add param to strip script tags from returned DocumentFragment
        /* str has to be an HTMLString
         * returns a DocumentFragment - https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
         * after inserting executes inline script and script tags */
        return document.createRange().createContextualFragment(str);
    },
    toggleClass(elmt, classnames) {
        // IE11 doesn't support https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/toggle
        classnames
            .trim()
            .split(/\s+/)
            .forEach(classname => elmt.classList.contains(classname) ? elmt.classList.remove(classname) : elmt.classList.add(classname));
        return elmt;
    },
    emptyNode(node) {
        while (node.firstChild) {
            node.removeChild(node.firstChild);
        }
        return node;
    },

    addScript(path, type = 'application/javascript', cb) {
        if (!document.querySelector(`script[src="${path}"]`)) {
            const script = document.createElement('script');
            script.src = path;
            script.type = type;
            document.head.appendChild(script);
            if (cb) {
                script.onload = cb;
            }
        }
    },
    ajax(ajaxConfig, panel) {
        let config,
            urlParts,
            xhr = new XMLHttpRequest();
        const configDefaults = {
            method: 'GET',
            async: true,
            user: '',
            pwd: '',
            done: function () {
                if (panel) {
                    let res = jsPanel.strToHtml(this.responseText);
                    if (config.urlSelector) {
                        res = res.querySelector(config.urlSelector);
                    }
                    panel.contentRemove();
                    panel.content.append(res);
                }
            },
            autoresize: true,
            autoreposition: true,
        };

        if (panel && typeof ajaxConfig === 'string') {
            config = Object.assign({}, configDefaults, {
                url: ajaxConfig,
            });
        } else if (typeof ajaxConfig === 'object' && ajaxConfig.url) {
            config = Object.assign({}, configDefaults, ajaxConfig);
            config.url = ajaxConfig.url;
            // reset timeout to 0, withCredentials & responseType to false if request is synchronous
            if (config.async === false) {
                config.timeout = 0;
                if (config.withCredentials) {
                    config.withCredentials = undefined;
                }
                if (config.responseType) {
                    config.responseType = undefined;
                }
            }
        } else {
            if (this.errorReporting) {
                let err = 'XMLHttpRequest seems to miss the <mark>url</mark> parameter!';
                jsPanel.errorpanel(err);
            }
            return;
        }
        // check url for added selector
        urlParts = config.url.trim().split(/\s+/);
        config.url = encodeURI(urlParts[0]);
        if (urlParts.length > 1) {
            urlParts.shift();
            config.urlSelector = urlParts.join(' ');
        }

        xhr.onreadystatechange = () => {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    panel ? config.done.call(xhr, xhr, panel) : config.done.call(xhr, xhr);
                } else {
                    if (config.fail) {
                        panel ? config.fail.call(xhr, xhr, panel) : config.fail.call(xhr, xhr);
                    }
                }

                if (config.always) {
                    panel ? config.always.call(xhr, xhr, panel) : config.always.call(xhr, xhr);
                }

                // resize and/or reposition panel if either width or height is set to 'auto'
                if (panel) {
                    if (config.autoresize || config.autoreposition) {
                        jsPanel.ajaxAutoresizeAutoreposition(panel, config);
                    }
                }

                // allows plugins to add callback functions to the ajax always callback
                if (jsPanel.ajaxAlwaysCallbacks.length) {
                    jsPanel.ajaxAlwaysCallbacks.forEach(item => {
                        panel ? item.call(xhr, xhr, panel) : item.call(xhr, xhr);
                    });
                }
            }
        };

        xhr.open(config.method, config.url, config.async, config.user, config.pwd);
        xhr.timeout = config.timeout || 0;
        if (config.withCredentials) {
            xhr.withCredentials = config.withCredentials;
        }
        if (config.responseType) {
            xhr.responseType = config.responseType;
        }
        if (config.beforeSend) {
            panel ? config.beforeSend.call(xhr, xhr, panel) : config.beforeSend.call(xhr, xhr);
        }
        config.data ? xhr.send(config.data) : xhr.send(null);
    },
    fetch(fetchConfig, panel) {
        let config;
        const configDefaults = {
            bodyMethod: 'text',
            autoresize: true,
            autoreposition: true,
            done: function (response, panel) {
                if (panel) {
                    let res = jsPanel.strToHtml(response);
                    panel.contentRemove();
                    panel.content.append(res);
                }
            },
        };
        if (panel && typeof fetchConfig === 'string') {
            config = Object.assign({}, configDefaults, {
                resource: encodeURI(fetchConfig),
            });
        } else if (typeof fetchConfig === 'object' && fetchConfig.resource) {
            config = Object.assign({}, configDefaults, fetchConfig);
            config.resource = encodeURI(fetchConfig.resource);
        } else {
            if (this.errorReporting) {
                let err = 'Fetch Request seems to miss the <mark>resource</mark> parameter!';
                jsPanel.errorpanel(err);
            }
            return;
        }

        const fetchInit = config.fetchInit || {};

        if (config.beforeSend) {
            panel ? config.beforeSend.call(fetchConfig, fetchConfig, panel) : config.beforeSend.call(fetchConfig, fetchConfig);
        }

        fetch(config.resource, fetchInit)
            .then(response => {
                if (response.ok) {
                    return response[config.bodyMethod]();
                }
            })
            .then(response => {
                panel ? config.done.call(response, response, panel) : config.done.call(response, response);
                if (panel) {
                    // resize and/or reposition panel if either width or height is set to 'auto'
                    if (config.autoresize || config.autoreposition) {
                        jsPanel.ajaxAutoresizeAutoreposition(panel, config);
                    }
                }
            });
    },
    ajaxAutoresizeAutoreposition(panel, ajaxOrFetchConfig) {
        const oContentSize = panel.options.contentSize;
        if (typeof oContentSize === 'string' && oContentSize.match(/auto/i)) {
            const parts = oContentSize.split(' '),
                sizes = Object.assign({}, { width: parts[0], height: parts[1] });
            if (ajaxOrFetchConfig.autoresize) {
                panel.resize(sizes);
            }
            if (!panel.classList.contains('jsPanel-contextmenu')) {
                if (ajaxOrFetchConfig.autoreposition) {
                    panel.reposition();
                }
            }
        } else if (typeof oContentSize === 'object' && (oContentSize.width === 'auto' || oContentSize.height === 'auto')) {
            const sizes = Object.assign({}, oContentSize);
            if (ajaxOrFetchConfig.autoresize) {
                panel.resize(sizes);
            }
            if (!panel.classList.contains('jsPanel-contextmenu')) {
                if (ajaxOrFetchConfig.autoreposition) {
                    panel.reposition();
                }
            }
        }
    },

    // templates and other elements ----------
    createPanelTemplate(dataAttr = true) {
        const panel = document.createElement('div');
        panel.className = 'jsPanel';
        panel.style.left = '0';
        panel.style.top = '0';
        if (dataAttr) {
            ['close', 'maximize', 'normalize', 'minimize', 'smallify'].forEach(item => {
                panel.setAttribute(`data-btn${item}`, 'enabled');
            });
        }
        panel.innerHTML = `<div class="jsPanel-hdr">
                                <div class="jsPanel-headerbar">
                                    <div class="jsPanel-headerlogo"></div>
                                    <div class="jsPanel-titlebar">
                                        <div class="jsPanel-title"></div>
                                    </div>
                                    <div class="jsPanel-controlbar">
                                        <button type="button" class="jsPanel-btn jsPanel-btn-smallify" aria-label="Smallify">${this.icons.smallify}</button>
                                        <button type="button" class="jsPanel-btn jsPanel-btn-minimize" aria-label="Minimize">${this.icons.minimize}</button>
                                        <button type="button" class="jsPanel-btn jsPanel-btn-normalize" aria-label="Normalize">${this.icons.normalize}</button>
                                        <button type="button" class="jsPanel-btn jsPanel-btn-maximize" aria-label="Maximize">${this.icons.maximize}</button>
                                        <button type="button" class="jsPanel-btn jsPanel-btn-close" aria-label="Close">${this.icons.close}</button>
                                    </div>
                                </div>
                                <div class="jsPanel-hdr-toolbar"></div>
                            </div>
                            <div class="jsPanel-progressbar">
                                <div class="jsPanel-progressbar-slider"></div>
                            </div>
                            <div class="jsPanel-content"></div>
                            <div class="jsPanel-minimized-box"></div>
                            <div class="jsPanel-ftr"></div>`;
        return panel;
    },
    createMinimizedTemplate() {
        const panel = document.createElement('div');
        panel.className = 'jsPanel-replacement';
        panel.innerHTML = `<div class="jsPanel-hdr">
                                <div class="jsPanel-headerbar">
                                    <div class="jsPanel-headerlogo"></div>
                                    <div class="jsPanel-titlebar">
                                        <div class="jsPanel-title"></div>
                                    </div>
                                    <div class="jsPanel-controlbar">
                                        <button type="button" class="jsPanel-btn jsPanel-btn-sm jsPanel-btn-normalize" aria-label="Normalize">${this.icons.normalize}</button>
                                        <button type="button" class="jsPanel-btn jsPanel-btn-sm jsPanel-btn-maximize" aria-label="Maximize">${this.icons.maximize}</button>
                                        <button type="button" class="jsPanel-btn jsPanel-btn-sm jsPanel-btn-close" aria-label="Close">${this.icons.close}</button>
                                    </div>
                                </div>
                            </div>`;
        return panel;
    },
    createSnapArea(panel, pos, snapsens) {
        const el = document.createElement('div'),
            parent = panel.parentElement;
        el.className = `jsPanel-snap-area jsPanel-snap-area-${pos}`;
        if (pos === 'lt' || pos === 'rt' || pos === 'rb' || pos === 'lb') {
            el.style.width = snapsens + 'px';
            el.style.height = snapsens + 'px';
        } else if (pos === 'ct' || pos === 'cb') {
            el.style.height = snapsens + 'px';
        } else if (pos === 'lc' || pos === 'rc') {
            el.style.width = snapsens + 'px';
        }
        if (parent !== document.body) {
            el.style.position = 'absolute';
        }
        if (!document.querySelector(`.jsPanel-snap-area.jsPanel-snap-area-${pos}`)) {
            panel.parentElement.appendChild(el);
        }
    },
    removeSnapAreas() {
        document.querySelectorAll('.jsPanel-snap-area').forEach(el => el.parentElement.removeChild(el));
    },

    // various helpers -------
    extend(obj) {
        // obj needs to be a plain object (to extend the individual panel, not the global object)
        if (Object.prototype.toString.call(obj) === '[object Object]') {
            for (let ext in obj) {
                if (Object.prototype.hasOwnProperty.call(obj, ext)) {
                    this.extensions[ext] = obj[ext];
                }
            }
        }
    },
    getPanels(condition = function() {return this.classList.contains('jsPanel-standard');}) {
        return Array.prototype.slice
            .call(document.querySelectorAll('.jsPanel'))
            .filter(value => condition.call(value, value))
            .sort((a, b) => b.style.zIndex - a.style.zIndex);
    },
    processCallbacks(panel, arg, someOrEvery = 'some', param, param2) {
        // if arg != array make it one
        if (typeof arg === 'function') {
            arg = [arg];
        }
        // some():  execute callbacks until one is found returning a truthy value
        // every(): execute callbacks until one is found returning a falsy value
        // truthy values are: '0' (string with single zero), 'false' (string with text false), [] (empty array), {} (empty object), function(){} ("empty" function)
        // falsy values are: false, 0, '', "", null, undefined, NaN
        if (someOrEvery) {
            return arg[someOrEvery](cb => cb.call(panel, panel, param, param2));
        } else {
            arg.forEach(cb => cb.call(panel, panel, param, param2));
        }
    },
    resetZi() {
        this.zi = ((startValue = jsPanel.ziBase) => {
            let val = startValue;
            return {
                next: () => val++
            };
        })();
        Array.prototype.slice
            .call(document.querySelectorAll('.jsPanel-standard'))
            .sort((a, b) => a.style.zIndex - b.style.zIndex)
            .forEach(panel => panel.style.zIndex = jsPanel.zi.next());
    },
    errorpanel(e) {
        this.create({
            paneltype: 'error',
            dragit: false,
            resizeit: false,
            theme: {
                bgPanel: 'white',
                bgContent: 'white',
                colorHeader: 'rebeccapurple',
                colorContent: '#333333',
                border: '2px solid rebeccapurple',
            },
            borderRadius: '.33rem',
            headerControls: 'closeonly xs',
            headerTitle: '&#9888; jsPanel Error',
            contentSize: { width: '50%', height: 'auto' },
            position: 'center-top 0 5 down',
            animateIn: 'jsPanelFadeIn',
            content: `<div class="jsPanel-error-content-separator"></div><p>${e}</p>`,
        });
    },

    // METHOD CREATING THE PANEL ---------------------------------------------
    create(options = {}, cb) {
        // initialize z-index generator
        if (!jsPanel.zi) {
            jsPanel.zi = ((startValue = jsPanel.ziBase) => {
                let val = startValue;
                return {
                    next: () => val++
                };
            })();
        }

        if (options.config) {
            options = Object.assign({}, this.defaults, options.config, options);
            delete options.config;
        } else {
            options = Object.assign({}, this.defaults, options);
        }
        if (!options.id) {
            options.id = `jsPanel-${(jsPanel.idCounter += 1)}`;
        } else if (typeof options.id === 'function') {
            options.id = options.id();
        }
        const p = document.getElementById(options.id);
        if (p !== null) {
            // if a panel with passed id already exists, front it and return false
            if (p.classList.contains('jsPanel')) {
                p.front();
            }
            if (this.errorReporting) {
                let err = `&#9664; COULD NOT CREATE NEW JSPANEL &#9658;<br>An element with the ID <mark>${options.id}</mark> already exists in the document.`;
                jsPanel.errorpanel(err);
            }
            return false;
        }

        // check whether container is valid -> if not return and throw error
        let panelContainer = this.pOcontainer(options.container);
        // panelContainer might be a NodeList, so use only first node in list
        if (typeof panelContainer === 'object' && panelContainer.length && panelContainer.length > 0) {
            panelContainer = panelContainer[0];
        }
        if (!panelContainer) {
            if (this.errorReporting) {
                let err = '&#9664; COULD NOT CREATE NEW JSPANEL &#9658;<br>The container to append the panel to does not exist';
                jsPanel.errorpanel(err);
            }
            return false;
        }

        // normalize on... callbacks
        // these callbacks must be an array of function(s) in order to be able to dynamically add/remove callbacks (for example in extensions)
        [
            'onbeforeclose',
            'onbeforemaximize',
            'onbeforeminimize',
            'onbeforenormalize',
            'onbeforesmallify',
            'onbeforeunsmallify',
            'onclosed',
            'onfronted',
            'onmaximized',
            'onminimized',
            'onnormalized',
            'onsmallified',
            'onstatuschange',
            'onunsmallified',
        ].forEach(item => {
            if (options[item]) {
                if (typeof options[item] === 'function') {
                    options[item] = [options[item]];
                }
            } else {
                options[item] = [];
            }
        });

        const self = options.template || this.createPanelTemplate();

        // Properties
        self.options = options;
        self.closetimer = undefined;
        self.status = 'initialized';
        self.currentData = {};
        self.header = self.querySelector('.jsPanel-hdr'); // complete header section
        self.headerbar = self.header.querySelector('.jsPanel-headerbar'); // log, title and controls
        self.titlebar = self.header.querySelector('.jsPanel-titlebar'); // div surrounding title div
        self.headerlogo = self.headerbar.querySelector('.jsPanel-headerlogo'); // logo only
        self.headertitle = self.headerbar.querySelector('.jsPanel-title'); // title div
        self.controlbar = self.headerbar.querySelector('.jsPanel-controlbar'); // div surrounding all controls
        self.headertoolbar = self.header.querySelector('.jsPanel-hdr-toolbar');
        self.content = self.querySelector('.jsPanel-content');
        self.footer = self.querySelector('.jsPanel-ftr');
        self.snappableTo = false;
        self.snapped = false;
        self.droppableTo = false;
        self.progressbar = self.autocloseProgressbar = self.querySelector('.jsPanel-progressbar'); // self.autocloseProgressbar kept for compatibility

        // Events
        const jspanelloaded = new CustomEvent('jspanelloaded', {
                detail: options.id,
                cancelable: true,
            }),
            jspanelstatuschange = new CustomEvent('jspanelstatuschange', {
                detail: options.id,
                cancelable: true,
            }),
            jspanelbeforenormalize = new CustomEvent('jspanelbeforenormalize', {
                detail: options.id,
                cancelable: true,
            }),
            jspanelnormalized = new CustomEvent('jspanelnormalized', {
                detail: options.id,
                cancelable: true,
            }),
            jspanelbeforemaximize = new CustomEvent('jspanelbeforemaximize', {
                detail: options.id,
                cancelable: true,
            }),
            jspanelmaximized = new CustomEvent('jspanelmaximized', {
                detail: options.id,
                cancelable: true,
            }),
            jspanelbeforeminimize = new CustomEvent('jspanelbeforeminimize', {
                detail: options.id,
                cancelable: true,
            }),
            jspanelminimized = new CustomEvent('jspanelminimized', {
                detail: options.id,
                cancelable: true,
            }),
            jspanelbeforesmallify = new CustomEvent('jspanelbeforesmallify', {
                detail: options.id,
                cancelable: true,
            }),
            jspanelsmallified = new CustomEvent('jspanelsmallified', {
                detail: options.id,
                cancelable: true,
            }),
            jspanelsmallifiedmax = new CustomEvent('jspanelsmallifiedmax', {
                detail: options.id,
                cancelable: true,
            }),
            jspanelbeforeunsmallify = new CustomEvent('jspanelbeforeunsmallify', {
                detail: options.id,
                cancelable: true,
            }),
            jspanelfronted = new CustomEvent('jspanelfronted', {
                detail: options.id,
                cancelable: true,
            }),
            jspanelbeforeclose = new CustomEvent('jspanelbeforeclose', {
                detail: options.id,
                cancelable: true,
            }),
            jspanelclosed = new CustomEvent('jspanelclosed', {
                detail: options.id,
                cancelable: true,
            }),
            jspanelcloseduser = new CustomEvent('jspanelcloseduser', {
                detail: options.id,
                cancelable: true,
            });
        // make panel available as event object property 'panel'
        [
            jspanelloaded,
            jspanelstatuschange,
            jspanelbeforenormalize,
            jspanelnormalized,
            jspanelbeforemaximize,
            jspanelmaximized,
            jspanelbeforeminimize,
            jspanelminimized,
            jspanelbeforesmallify,
            jspanelsmallified,
            jspanelsmallifiedmax,
            jspanelbeforeunsmallify,
            jspanelfronted,
            jspanelbeforeclose,
        ].forEach(evt => evt.panel = self);

        // controls buttons
        const closeBtn = self.querySelector('.jsPanel-btn-close'),
            maxBtn = self.querySelector('.jsPanel-btn-maximize'),
            normBtn = self.querySelector('.jsPanel-btn-normalize'),
            smallBtn = self.querySelector('.jsPanel-btn-smallify'),
            minBtn = self.querySelector('.jsPanel-btn-minimize');

        if (closeBtn) {
            jsPanel.pointerup.forEach(item => {
                closeBtn.addEventListener(item, e => {
                    e.preventDefault();
                    // disable close for all mouse buttons but left
                    if (e.button && e.button > 0) {
                        return false;
                    }
                    self.close(null, true); // true indicates panel closed by using the close control
                });
            });
        }
        if (maxBtn) {
            jsPanel.pointerup.forEach(item => {
                maxBtn.addEventListener(item, e => {
                    e.preventDefault();
                    if (e.button && e.button > 0) {
                        return false;
                    }
                    self.maximize();
                });
            });
        }
        if (normBtn) {
            jsPanel.pointerup.forEach(item => {
                normBtn.addEventListener(item, e => {
                    e.preventDefault();
                    if (e.button && e.button > 0) {
                        return false;
                    }
                    self.normalize();
                });
            });
        }
        if (smallBtn) {
            jsPanel.pointerup.forEach(item => {
                smallBtn.addEventListener(item, e => {
                    e.preventDefault();
                    if (e.button && e.button > 0) {
                        return false;
                    }
                    if (self.status === 'normalized' || self.status === 'maximized') {
                        self.smallify();
                    } else if (self.status === 'smallified' || self.status === 'smallifiedmax') {
                        self.unsmallify();
                    }
                });
            });
        }
        if (minBtn) {
            jsPanel.pointerup.forEach(item => {
                minBtn.addEventListener(item, e => {
                    e.preventDefault();
                    if (e.button && e.button > 0) {
                        return false;
                    }
                    self.minimize();
                });
            });
        }

        // import extensions (extensions of the individual panel, not the global object jsPanel)
        let extensions = jsPanel.extensions;
        for (let ext in extensions) {
            if (Object.prototype.hasOwnProperty.call(extensions, ext)) {
                self[ext] = extensions[ext];
            }
        }

        // panel methods
        self.setBorder = val => {
            let border = jsPanel.pOborder(val);
            if (!border[2].length) {
                border[2] = self.style.backgroundColor;
            }
            border = border.join(' ');
            self.style.border = border;
            self.options.border = border;
            return self;
        };
        self.setBorderRadius = rad => {
            if (typeof rad === 'string') {
                if (rad.startsWith('--') || rad.startsWith('var')) {
                    rad = rad.replace(/\s*\(\s*/g, '(') // remove whitespace around opening brackets
                        .replace(/\s*\)/g, ')')         // remove whitespace around closing brackets
                        .replace(/\s+/g, ' ');          // replace all other whitespace(s) with a single whitespace
                    rad = jsPanel.getCssVariableValue(rad);
                }
            }
            if (typeof rad === 'number') {
                rad += 'px';
            }
            self.style.borderRadius = rad;
            const br = getComputedStyle(self);
            // set border-radius of either header or content section depending on presence of header
            if (self.options.header) {
                self.header.style.borderTopLeftRadius = br.borderTopLeftRadius;
                self.header.style.borderTopRightRadius = br.borderTopRightRadius;
            } else {
                self.content.style.borderTopLeftRadius = br.borderTopLeftRadius;
                self.content.style.borderTopRightRadius = br.borderTopRightRadius;
            }
            // set border-radius of either footer or content section depending on presence of footer
            if (self.options.footerToolbar) {
                self.footer.style.borderBottomRightRadius = br.borderBottomRightRadius;
                self.footer.style.borderBottomLeftRadius = br.borderBottomLeftRadius;
            } else {
                self.content.style.borderBottomRightRadius = br.borderBottomRightRadius;
                self.content.style.borderBottomLeftRadius = br.borderBottomLeftRadius;
            }
            return self;
        };
        self.setTheme = (theme = options.theme, cb) => {
            // if panel is minimized normalize it for theme change
            let minimized;
            if (self.status === 'minimized') {
                minimized = true;
                self.normalize();
            }
            // first remove all theme related styles
            jsPanel.clearTheme(self);
            if (typeof theme === 'object') {
                options.border = undefined;
                jsPanel.applyCustomTheme(self, theme);
            } else if (typeof theme === 'string') {
                if (theme === 'none') {
                    theme = 'white';
                }
                let themeDetails = jsPanel.getThemeDetails(theme);
                jsPanel.applyColorTheme(self, themeDetails);
            }
            // minimize again if panel was minimized prior theme change
            if (minimized) {
                self.minimize();
            }
            if (cb) {
                cb.call(self, self);
            }
            return self;
        };
        self.remove = (id, closedBy, cb) => {
            // self.remove() is just a helper func used in self.close()
            self.parentElement.removeChild(self);
            if (!document.getElementById(id)) {
                self.removeMinimizedReplacement();
                self.status = 'closed';
                if (closedBy) {
                    document.dispatchEvent(jspanelcloseduser);
                }
                document.dispatchEvent(jspanelclosed);
                if (self.options.onclosed) {
                    jsPanel.processCallbacks(self, self.options.onclosed, 'every', closedBy);
                }
                jsPanel.autopositionRemaining(self);
                if (cb) {
                    cb.call(id, id);
                }
            } else {
                if (cb) {
                    cb.call(self, id, self);
                }
            }
            window.removeEventListener('resize', self.windowResizeHandler);
            document.removeEventListener('jspanelresize', self.parentResizeHandler);
        };
        self.close = (cb, closedByUser) => {
            // if panel does not exist return
            if (!self.parentElement) {return;}

            if (self.closetimer) {
                window.clearInterval(self.closetimer);
            }
            document.dispatchEvent(jspanelbeforeclose);
            self.statusBefore = self.status;

            if (
                self.options.onbeforeclose &&
                self.options.onbeforeclose.length > 0 &&
                !jsPanel.processCallbacks(self, self.options.onbeforeclose, 'some', self.status, closedByUser)
            ) {
                return self;
            }
            if (self.options.animateOut) {
                if (self.options.animateIn) {
                    jsPanel.remClass(self, self.options.animateIn);
                }
                jsPanel.setClass(self, self.options.animateOut);
                self.addEventListener('animationend', e => {
                    e.stopPropagation();
                    self.remove(self.id, closedByUser, cb);
                });
            } else {
                self.remove(self.id, closedByUser, cb);
            }
        };
        self.maximize = (cb, donotfront) => {
            // Note: do not disable maximize method for already maximized panels -> onContainerResize wouldn't work
            self.statusBefore = self.status;
            if (
                options.onbeforemaximize &&
                options.onbeforemaximize.length > 0 &&
                !jsPanel.processCallbacks(self, options.onbeforemaximize, 'some', self.statusBefore)
            ) {
                return self;
            }

            document.dispatchEvent(jspanelbeforemaximize);

            const parent = self.parentElement,
                margins = jsPanel.pOcontainment(options.maximizedMargin); // normalize maximizedMargin

            if (parent === document.body) {
                // maximize within window
                /*
                   When clientHeight is used on the root element (the <html> element), (or on <body> if the document is in quirks mode),
                   the viewport's height (excluding any scrollbar) is returned. This is a special case of clientHeight.
                   See https://developer.mozilla.org/en-US/docs/Web/API/Element/clientHeight
                   document.documentElement in the code below returns the <html> element
                */
                self.style.width = document.documentElement.clientWidth - margins[1] - margins[3] + 'px';
                self.style.height = document.documentElement.clientHeight - margins[0] - margins[2] + 'px';
                self.style.left = margins[3] + 'px';
                self.style.top = margins[0] + 'px';
            } else {
                // maximize within parentElement
                self.style.width = parent.clientWidth - margins[1] - margins[3] + 'px';
                self.style.height = parent.clientHeight - margins[0] - margins[2] + 'px';
                self.style.left = margins[3] + 'px';
                self.style.top = margins[0] + 'px';
            }

            smallBtn.style.transform = 'unset';
            self.removeMinimizedReplacement();
            self.status = 'maximized';
            self.setControls(['.jsPanel-btn-maximize']);
            if (!donotfront) {
                self.front();
            }
            document.dispatchEvent(jspanelmaximized);
            document.dispatchEvent(jspanelstatuschange);

            if (options.onstatuschange) {
                jsPanel.processCallbacks(self, options.onstatuschange, 'every', self.statusBefore);
            }

            if (cb) {
                cb.call(self, self, self.statusBefore);
            }

            if (options.onmaximized) {
                jsPanel.processCallbacks(self, options.onmaximized, 'every', self.statusBefore);
            }

            return self;
        };
        self.minimize = cb => {
            if (self.status === 'minimized') {
                return self;
            }
            self.statusBefore = self.status;

            if (
                options.onbeforeminimize &&
                options.onbeforeminimize.length > 0 &&
                !jsPanel.processCallbacks(self, options.onbeforeminimize, 'some', self.statusBefore)
            ) {
                return self;
            }

            document.dispatchEvent(jspanelbeforeminimize);

            // create container for minimized replacements if not already there
            if (!document.getElementById('jsPanel-replacement-container')) {
                const replacementContainer = document.createElement('div');
                replacementContainer.id = 'jsPanel-replacement-container';
                document.body.append(replacementContainer);
            }

            self.style.left = '-9999px';
            self.status = 'minimized';
            document.dispatchEvent(jspanelminimized);
            document.dispatchEvent(jspanelstatuschange);

            if (options.onstatuschange) {
                jsPanel.processCallbacks(self, options.onstatuschange, 'every', self.statusBefore);
            }

            if (options.minimizeTo) {
                let replacement = self.createMinimizedReplacement(),
                    container,
                    parent,
                    list;
                switch (options.minimizeTo) {
                    case 'default':
                        document.getElementById('jsPanel-replacement-container').append(replacement);
                        break;
                    case 'parentpanel':
                        parent = self.closest('.jsPanel-content').parentElement;
                        list = parent.querySelectorAll('.jsPanel-minimized-box');
                        container = list[list.length - 1];
                        container.append(replacement);
                        break;
                    case 'parent':
                        parent = self.parentElement;
                        container = parent.querySelector('.jsPanel-minimized-container');
                        if (!container) {
                            container = document.createElement('div');
                            container.className = 'jsPanel-minimized-container';
                            parent.append(container);
                        }
                        container.append(replacement);
                        break;
                    default:
                        // all other strings are assumed to be selector strings returning a single element to append the min replacement to
                        document.querySelector(options.minimizeTo).append(replacement);
                }
            }

            if (cb) {
                cb.call(self, self, self.statusBefore);
            }

            if (options.onminimized) {
                jsPanel.processCallbacks(self, options.onminimized, 'every', self.statusBefore);
            }

            return self;
        };
        self.normalize = cb => {
            if (self.status === 'normalized') {
                return self;
            }
            self.statusBefore = self.status;
            // ensure smallify/unsmallify transition is turned off when resizing begins
            //self.style.transition = 'unset';

            if (
                options.onbeforenormalize &&
                options.onbeforenormalize.length > 0 &&
                !jsPanel.processCallbacks(self, options.onbeforenormalize, 'some', self.statusBefore)
            ) {
                return self;
            }

            document.dispatchEvent(jspanelbeforenormalize);
            self.style.width = self.currentData.width;
            self.style.height = self.currentData.height;
            if (self.snapped) {
                // if panel is snapped before minimizing restore snapped position
                self.snap(self.snapped, true);
            } else {
                self.style.left = self.currentData.left;
                self.style.top = self.currentData.top;
            }
            smallBtn.style.transform = 'unset';
            self.removeMinimizedReplacement();
            self.status = 'normalized';
            self.setControls(['.jsPanel-btn-normalize']);
            self.front();
            document.dispatchEvent(jspanelnormalized);
            document.dispatchEvent(jspanelstatuschange);

            if (options.onstatuschange) {
                jsPanel.processCallbacks(self, options.onstatuschange, 'every', self.statusBefore);
            }

            if (cb) {
                cb.call(self, self, self.statusBefore);
            }

            if (options.onnormalized) {
                jsPanel.processCallbacks(self, options.onnormalized, 'every', self.statusBefore);
            }

            return self;
        };
        self.smallify = cb => {
            if (self.status === 'smallified' || self.status === 'smallifiedmax') {
                return self;
            }
            self.statusBefore = self.status;

            if (
                options.onbeforesmallify &&
                options.onbeforesmallify.length > 0 &&
                !jsPanel.processCallbacks(self, options.onbeforesmallify, 'some', self.statusBefore)
            ) {
                return self;
            }

            document.dispatchEvent(jspanelbeforesmallify);

            self.style.overflow = 'hidden';
            const selfStyles = window.getComputedStyle(self),
                selfHeaderHeight = parseFloat(window.getComputedStyle(self.headerbar).height);
            self.style.height = parseFloat(selfStyles.borderTopWidth) + parseFloat(selfStyles.borderBottomWidth) + selfHeaderHeight + 'px';

            smallBtn.style.transform = 'rotate(180deg)';

            if (self.status === 'normalized') {
                self.setControls(['.jsPanel-btn-normalize']);
                self.status = 'smallified';
                document.dispatchEvent(jspanelsmallified);
                document.dispatchEvent(jspanelstatuschange);
                if (options.onstatuschange) {
                    jsPanel.processCallbacks(self, options.onstatuschange, 'every', self.statusBefore);
                }
            } else if (self.status === 'maximized') {
                self.setControls(['.jsPanel-btn-maximize']);
                self.status = 'smallifiedmax';
                document.dispatchEvent(jspanelsmallifiedmax);
                document.dispatchEvent(jspanelstatuschange);
                if (options.onstatuschange) {
                    jsPanel.processCallbacks(self, options.onstatuschange, 'every', self.statusBefore);
                }
            }

            const minBoxes = self.querySelectorAll('.jsPanel-minimized-box');
            minBoxes[minBoxes.length - 1].style.display = 'none';

            if (cb) {
                cb.call(self, self, self.statusBefore);
            }

            if (options.onsmallified) {
                jsPanel.processCallbacks(self, options.onsmallified, 'every', self.statusBefore);
            }

            return self;
        };
        self.unsmallify = cb => {
            self.statusBefore = self.status;

            if (self.status === 'smallified' || self.status === 'smallifiedmax') {
                if (
                    options.onbeforeunsmallify &&
                    options.onbeforeunsmallify.length > 0 &&
                    !jsPanel.processCallbacks(self, options.onbeforeunsmallify, 'some', self.statusBefore)
                ) {
                    return self;
                }

                document.dispatchEvent(jspanelbeforeunsmallify);
                self.style.overflow = 'visible';
                self.front();

                if (self.status === 'smallified') {
                    self.style.height = self.currentData.height;
                    self.setControls(['.jsPanel-btn-normalize']);
                    self.status = 'normalized';
                    document.dispatchEvent(jspanelnormalized);
                    document.dispatchEvent(jspanelstatuschange);
                    if (options.onstatuschange) {
                        jsPanel.processCallbacks(self, options.onstatuschange, 'every', self.statusBefore);
                    }
                } else if (self.status === 'smallifiedmax') {
                    self.maximize();
                } else if (self.status === 'minimized') {
                    self.normalize();
                }
                smallBtn.style.transform = 'rotate(0deg)';

                const minBoxes = self.querySelectorAll('.jsPanel-minimized-box');
                minBoxes[minBoxes.length - 1].style.display = 'flex';

                if (cb) {
                    cb.call(self, self, self.statusBefore);
                }

                if (options.onunsmallified) {
                    jsPanel.processCallbacks(self, options.onunsmallified, 'every', self.statusBefore);
                }
            }

            return self;
        };
        self.front = (callback, execOnFrontedCallbacks = true) => {
            if (self.status === 'minimized') {
                self.statusBefore === 'maximized' ? self.maximize() : self.normalize();
            } else {
                const newArr = Array.prototype.slice.call(document.querySelectorAll('.jsPanel-standard')).map(panel => panel.style.zIndex);
                if (Math.max(...newArr) > self.style.zIndex) {
                    self.style.zIndex = jsPanel.zi.next();
                }
                jsPanel.resetZi();
            }
            document.dispatchEvent(jspanelfronted);
            if (callback) {
                callback.call(self, self);
            }
            if (options.onfronted && execOnFrontedCallbacks) {
                jsPanel.processCallbacks(self, options.onfronted, 'every', self.status);
            }
            return self;
        };
        self.snap = (pos, alreadySnapped = false) => {
            // store panel size before it snaps, only if not snapped already
            if (!alreadySnapped) {
                self.currentData.beforeSnap = {
                    width: self.currentData.width,
                    height: self.currentData.height,
                };
            }
            // snap panel
            if (pos && typeof pos === 'function' && !alreadySnapped) {
                pos.call(self, self, self.snappableTo);
            } else if (pos !== false) {
                let offsets = [0, 0];
                if (self.options.dragit.snap.containment) {
                    if (self.options.dragit.containment) {
                        const containment = jsPanel.pOcontainment(self.options.dragit.containment),
                            position = self.snappableTo;
                        if (position.startsWith('left')) {
                            offsets[0] = containment[3];
                        } else if (position.startsWith('right')) {
                            offsets[0] = -containment[1];
                        }
                        if (position.endsWith('top')) {
                            offsets[1] = containment[0];
                        } else if (position.endsWith('bottom')) {
                            offsets[1] = -containment[2];
                        }
                    }
                }
                self.reposition(`${self.snappableTo} ${offsets[0]} ${offsets[1]}`);
            }
            if (!alreadySnapped) {
                self.snapped = self.snappableTo;
            }
        };
        self.move = (target, cb) => {
            let overlaps = self.overlaps(target, 'paddingbox'),
                source = self.parentElement;
            target.appendChild(self);
            self.options.container = target;
            self.style.left = overlaps.left + 'px';
            self.style.top = overlaps.top + 'px';
            self.saveCurrentDimensions();
            self.saveCurrentPosition();
            self.calcSizeFactors(); // important for option.onContainerResize
            if (cb) {
                cb.call(self, self, target, source);
            }
            return self;
        };

        self.closeChildpanels = cb => {
            self.getChildpanels().forEach(item => item.close());
            if (cb) {
                cb.call(self, self);
            }
            return self;
        };
        self.getChildpanels = cb => {
            const childpanels = self.content.querySelectorAll('.jsPanel');
            if (cb) {
                childpanels.forEach((panel, index, list) => {
                    cb.call(panel, panel, index, list);
                });
            }
            return childpanels;
        };
        self.isChildpanel = cb => {
            const pp = self.closest('.jsPanel-content'),
                parentpanel = pp ? pp.parentElement : null;
            if (cb) {
                cb.call(self, self, parentpanel);
            }
            // if panel is childpanel of another panel returns parentpanel, otherwise false
            return pp ? parentpanel : false;
        };
        self.contentRemove = cb => {
            jsPanel.emptyNode(self.content);
            if (cb) {
                cb.call(self, self);
            }
            return self;
        };
        self.createMinimizedReplacement = () => {
            const tpl = jsPanel.createMinimizedTemplate(),
                color = window.getComputedStyle(self.headertitle).color,
                selfStyles = window.getComputedStyle(self),
                font = options.iconfont,
                controlbar = tpl.querySelector('.jsPanel-controlbar');

            // if panel background is an image (that includes gradients) instead of a color value
            if (self.options.header !== 'auto-show-hide') {
                jsPanel.setStyles(tpl, {
                    backgroundColor: selfStyles.backgroundColor,
                    backgroundPositionX: selfStyles.backgroundPositionX,
                    backgroundPositionY: selfStyles.backgroundPositionY,
                    backgroundRepeat: selfStyles.backgroundRepeat,
                    backgroundAttachment: selfStyles.backgroundAttachment,
                    backgroundImage: selfStyles.backgroundImage,
                    backgroundSize: selfStyles.backgroundSize,
                    backgroundOrigin: selfStyles.backgroundOrigin,
                    backgroundClip: selfStyles.backgroundClip,
                });
            } else {
                tpl.style.backgroundColor = window.getComputedStyle(self.header).backgroundColor;
            }

            tpl.id = self.id + '-min';
            tpl.querySelector('.jsPanel-headerbar').replaceChild(self.headerlogo.cloneNode(true), tpl.querySelector('.jsPanel-headerlogo'));
            tpl.querySelector('.jsPanel-titlebar').replaceChild(self.headertitle.cloneNode(true), tpl.querySelector('.jsPanel-title'));
            tpl.querySelector('.jsPanel-titlebar').setAttribute('title', self.headertitle.textContent);
            tpl.querySelector('.jsPanel-title').style.color = color;
            controlbar.style.color = color;

            controlbar.querySelectorAll('button').forEach(btn => btn.style.color = color);

            ['jsPanel-hdr-dark', 'jsPanel-hdr-light'].forEach(item => {
                if (self.header.classList.contains(item)) {
                    tpl.querySelector('.jsPanel-hdr').classList.add(item);
                }
            });

            // set iconfont
            self.setIconfont(font, tpl);

            if (self.dataset.btnnormalize === 'enabled') {
                jsPanel.pointerup.forEach(evt => {
                    tpl.querySelector('.jsPanel-btn-normalize').addEventListener(evt, e => {
                        e.preventDefault();
                        if (e.button && e.button > 0) {
                            return false;
                        }
                        self.normalize();
                    });
                });
            } else {
                controlbar.querySelector('.jsPanel-btn-normalize').style.display = 'none';
            }
            if (self.dataset.btnmaximize === 'enabled') {
                jsPanel.pointerup.forEach(evt => {
                    tpl.querySelector('.jsPanel-btn-maximize').addEventListener(evt, e => {
                        e.preventDefault();
                        if (e.button && e.button > 0) {
                            return false;
                        }
                        self.maximize();
                    });
                });
            } else {
                controlbar.querySelector('.jsPanel-btn-maximize').style.display = 'none';
            }
            if (self.dataset.btnclose === 'enabled') {
                jsPanel.pointerup.forEach(evt => {
                    tpl.querySelector('.jsPanel-btn-close').addEventListener(evt, e => {
                        e.preventDefault();
                        if (e.button && e.button > 0) {
                            return false;
                        }
                        self.close(null, true);
                    });
                });
            } else {
                controlbar.querySelector('.jsPanel-btn-close').style.display = 'none';
            }

            return tpl;
        };
        self.removeMinimizedReplacement = () => {
            const elmt = document.getElementById(`${self.id}-min`);
            if (elmt) {
                elmt.parentElement.removeChild(elmt);
            }
        };

        self.drag = (options = {}) => {
            let dragstarted, dragElmt, opts;
            const jspaneldragstart = new CustomEvent('jspaneldragstart', { detail: self.id }),
                jspaneldrag = new CustomEvent('jspaneldrag', { detail: self.id }),
                jspaneldragstop = new CustomEvent('jspaneldragstop', { detail: self.id });

            // make panel available as event object property 'panel'
            [jspaneldragstart, jspaneldrag, jspaneldragstop].forEach(evt => evt.panel = self);

            const camelcase = string => {
                // 'left-top' converted to 'snapLeftTop'
                let str = string.split('-');
                str.forEach((word, index) => {
                    str[index] = word.charAt(0).toUpperCase() + word.slice(1);
                });
                return 'snap' + str.join('');
            };
            function windowListener(e) {
                if (e.relatedTarget === null) {
                    jsPanel.pointermove.forEach(evt => {
                        document.removeEventListener(evt, dragElmt, false);
                        self.style.opacity = 1;
                    });
                }
            }
            // attach handler to each drag handle
            let handles = options.handles || jsPanel.defaults.dragit.handles;
            let cursor = options.cursor || jsPanel.defaults.dragit.cursor;

            function pointerUpHandlerDragit(e) {
                jsPanel.pointermove.forEach(e => document.removeEventListener(e, dragElmt));
                jsPanel.removeSnapAreas();
                if (dragstarted) {
                    self.style.opacity = 1;
                    dragstarted = undefined;
                    if (opts.snap) {
                        switch (self.snappableTo) {
                            case 'left-top':
                                self.snap(opts.snap.snapLeftTop);
                                break;
                            case 'center-top':
                                self.snap(opts.snap.snapCenterTop);
                                break;
                            case 'right-top':
                                self.snap(opts.snap.snapRightTop);
                                break;
                            case 'right-center':
                                self.snap(opts.snap.snapRightCenter);
                                break;
                            case 'right-bottom':
                                self.snap(opts.snap.snapRightBottom);
                                break;
                            case 'center-bottom':
                                self.snap(opts.snap.snapCenterBottom);
                                break;
                            case 'left-bottom':
                                self.snap(opts.snap.snapLeftBottom);
                                break;
                            case 'left-center':
                                self.snap(opts.snap.snapLeftCenter);
                                break;
                        }

                        if (opts.snap.callback && self.snappableTo && typeof opts.snap.callback === 'function') {
                            opts.snap.callback.call(self, self);
                            if (opts.snap.repositionOnSnap && opts.snap[camelcase(self.snappableTo)] !== false) {
                                self.repositionOnSnap(self.snappableTo);
                            }
                        }
                        if (self.snappableTo && opts.snap.repositionOnSnap && opts.snap[camelcase(self.snappableTo)]) {
                            self.repositionOnSnap(self.snappableTo);
                        }
                    }
                    // opts.drop
                    if (self.droppableTo && self.droppableTo) {
                        let sourceContainer = self.parentElement;
                        self.move(self.droppableTo);
                        if (opts.drop.callback) {
                            opts.drop.callback.call(self, self, self.droppableTo, sourceContainer);
                        }
                    }
                    document.dispatchEvent(jspaneldragstop);
                    if (opts.stop.length) {
                        let stopStyles = window.getComputedStyle(self),
                            paneldata = {
                                left: parseFloat(stopStyles.left),
                                top: parseFloat(stopStyles.top),
                                width: parseFloat(stopStyles.width),
                                height: parseFloat(stopStyles.height),
                            };
                        jsPanel.processCallbacks(self, opts.stop, false, paneldata, e);
                    }
                    self.saveCurrentPosition();
                    self.calcSizeFactors(); // important for options onwindowresize/onparentresize
                }
                self.controlbar.style.pointerEvents = 'inherit';
                self.content.style.pointerEvents = 'inherit';
                // restore other panel's css pointer-events
                document.querySelectorAll('iframe').forEach(frame => frame.style.pointerEvents = 'auto');
                document.removeEventListener(e, pointerUpHandlerDragit);
            }

            self.querySelectorAll(handles).forEach(handle => {
                handle.style.touchAction = 'none';
                handle.style.cursor = cursor;

                jsPanel.pointerdown.forEach(evt => {
                    handle.addEventListener(evt, e => {
                        // disable dragging for all mouse buttons but left
                        if (e.button && e.button > 0) {
                            return false;
                        }

                        // setup and normalize dragit options
                        opts = Object.assign({}, jsPanel.defaults.dragit, options);

                        if (opts.disableOnMaximized && self.status === 'maximized') {
                            return false;
                        }

                        if (opts.containment || opts.containment === 0) {
                            opts.containment = jsPanel.pOcontainment(opts.containment);
                        }

                        if (opts.grid) {
                            if (Array.isArray(opts.grid)) {
                                if (opts.grid.length === 1) {
                                    opts.grid[1] = opts.grid[0];
                                }
                            }
                        }

                        if (opts.snap) {
                            if (typeof opts.snap === 'object') {
                                opts.snap = Object.assign({}, jsPanel.defaultSnapConfig, opts.snap);
                            } else {
                                opts.snap = jsPanel.defaultSnapConfig;
                            }
                        }

                        // footer elmts with the class "jsPanel-ftr-btn" don't drag a panel
                        // do not compare e.target with e.currentTarget because there might be footer elmts supposed to drag the panel
                        // noinspection JSUnresolvedFunction
                        if (e.target.closest('.jsPanel-ftr-btn')) {
                            return;
                        }

                        self.controlbar.style.pointerEvents = 'none';
                        self.content.style.pointerEvents = 'none'; // without this code handler might not be unbound when content has iframe or object tag
                        // prevents iframes in other panel from interfering with drag action of dragged panel
                        document.querySelectorAll('iframe').forEach(frame => frame.style.pointerEvents = 'none');

                        let startStyles = window.getComputedStyle(self),
                            startLeft = parseFloat(startStyles.left),
                            startTop = parseFloat(startStyles.top),
                            startWidth = parseFloat(startStyles.width),
                            startHeight = parseFloat(startStyles.height),
                            psx = e.touches ? e.touches[0].clientX : e.clientX, // pointer x on mousedown (don't use pageX, doesn't work on FF for Android)
                            psy = e.touches ? e.touches[0].clientY : e.clientY, // same as above
                            parent = self.parentElement,
                            parentRect = parent.getBoundingClientRect(),
                            parentStyles = window.getComputedStyle(parent),
                            scaleFactor = self.getScaleFactor(),
                            startLeftCorrection = 0,
                            scrollbarwidths = jsPanel.getScrollbarWidth(parent);

                        // function actually dragging the elmt
                        dragElmt = e => {
                            e.preventDefault();

                            if (!dragstarted) {
                                document.dispatchEvent(jspaneldragstart);
                                self.style.opacity = opts.opacity;
                                // if configured restore panel size to a value before snap and reposition reasonable before drag actually starts
                                if (self.snapped && opts.snap.resizeToPreSnap && self.currentData.beforeSnap) {
                                    self.resize(self.currentData.beforeSnap.width + ' ' + self.currentData.beforeSnap.height);
                                    self.setControls(['.jsPanel-btn-normalize']);
                                    let intermediateStyles = self.getBoundingClientRect(),
                                        delta = psx - (intermediateStyles.left + intermediateStyles.width),
                                        wHalf = intermediateStyles.width / 2;
                                    if (delta > -wHalf) {
                                        startLeftCorrection = delta + wHalf;
                                    }
                                }

                                self.front();
                                self.snapped = false;

                                // panel is maximized on dragstart
                                if (self.status === 'maximized') {
                                    self.setControls(['.jsPanel-btn-normalize']);
                                    self.status = 'normalized';
                                }

                                // opts.drop
                                if (opts.drop && opts.drop.dropZones) {
                                    //opts.drop.dropZones = opts.drop.dropZones.map(zone => jsPanel.pOcontainer(zone));
                                    let dropzones = opts.drop.dropZones.map(zone => jsPanel.pOcontainer(zone)); // -> array where each item is a NodeList
                                    let dropzonelist = [];
                                    dropzones.forEach(function (nodelist) {
                                        if (nodelist.length) {
                                            // an element node does not have a length property
                                            nodelist.forEach(function (node) {
                                                dropzonelist.push(node);
                                            });
                                        } else {
                                            dropzonelist.push(nodelist);
                                        }
                                    });
                                    // filter list to have only unique values
                                    dropzonelist = dropzonelist.filter(function (value, index, self) {
                                        return self.indexOf(value) === index;
                                    });
                                    opts.drop.dropZones = dropzonelist;
                                }

                                // dragstart callback
                                if (opts.start.length) {
                                    jsPanel.processCallbacks(
                                        self,
                                        opts.start,
                                        false,
                                        {
                                            left: startLeft,
                                            top: startTop,
                                            width: startWidth,
                                            height: startHeight,
                                        },
                                        e
                                    );
                                }
                            }
                            dragstarted = 1;
                            let elmtL, elmtL2, elmtT, elmtT2, elmtR, elmtR2, elmtB, elmtB2, right, bottom;
                            let pmx = e.touches ? e.touches[0].clientX : e.clientX, // current pointer x while pointer moves (don't use pageX, doesn't work on FF for Android)
                                pmy = e.touches ? e.touches[0].clientY : e.clientY, // current pointer y while pointer moves (don't use pageY, doesn't work on FF for Android)
                                dragStyles = window.getComputedStyle(self), // get current styles while dragging
                                overlaps;

                            // EDGE reports "auto" instead of pixel value using getComputedStyle(), so some values need to be calculated different
                            // this whole block of code could be removed if EDGE not based on Chromium doesn't need to be supported
                            if (parent === document.body) {
                                let elmtRect = self.getBoundingClientRect();
                                right =
                                    window.innerWidth -
                                    parseInt(parentStyles.borderLeftWidth, 10) -
                                    parseInt(parentStyles.borderRightWidth, 10) -
                                    (elmtRect.left + elmtRect.width);
                                bottom =
                                    window.innerHeight -
                                    parseInt(parentStyles.borderTopWidth, 10) -
                                    parseInt(parentStyles.borderBottomWidth, 10) -
                                    (elmtRect.top + elmtRect.height);
                            } else {
                                right =
                                    parseInt(parentStyles.width, 10) -
                                    parseInt(parentStyles.borderLeftWidth, 10) -
                                    parseInt(parentStyles.borderRightWidth, 10) -
                                    (parseInt(dragStyles.left, 10) + parseInt(dragStyles.width, 10));
                                bottom =
                                    parseInt(parentStyles.height, 10) -
                                    parseInt(parentStyles.borderTopWidth, 10) -
                                    parseInt(parentStyles.borderBottomWidth, 10) -
                                    (parseInt(dragStyles.top, 10) + parseInt(dragStyles.height, 10));
                            }
                            // -- -- --

                            elmtL = parseFloat(dragStyles.left);
                            elmtT = parseFloat(dragStyles.top);
                            elmtR = right; // replace line with parseFloat(dragStyles.right); if EDGE code block above is removed
                            elmtB = bottom; // replace line with parseFloat(dragStyles.bottom); if EDGE code block above is removed

                            if (opts.snap) {
                                if (opts.snap.trigger === 'panel') {
                                    elmtL2 = elmtL ** 2;
                                    elmtT2 = elmtT ** 2;
                                    elmtR2 = elmtR ** 2;
                                    elmtB2 = elmtB ** 2;
                                } else if (opts.snap.trigger === 'pointer') {
                                    if (self.options.container === 'window') {
                                        elmtL = pmx;
                                        elmtT = pmy;
                                        elmtR = window.innerWidth - pmx;
                                        elmtB = window.innerHeight - pmy;
                                        elmtL2 = pmx ** 2;
                                        elmtT2 = elmtT ** 2;
                                        elmtR2 = elmtR ** 2;
                                        elmtB2 = elmtB ** 2;
                                    } else {
                                        overlaps = self.overlaps(parent, 'paddingbox', e);
                                        elmtL = overlaps.pointer.left;
                                        elmtT = overlaps.pointer.top;
                                        elmtR = overlaps.pointer.right;
                                        elmtB = overlaps.pointer.bottom;
                                        elmtL2 = overlaps.pointer.left ** 2;
                                        elmtT2 = overlaps.pointer.top ** 2;
                                        elmtR2 = overlaps.pointer.right ** 2;
                                        elmtB2 = overlaps.pointer.bottom ** 2;
                                    }
                                }
                            }

                            let lefttopVectorDrag = Math.sqrt(elmtL2 + elmtT2),
                                leftbottomVectorDrag = Math.sqrt(elmtL2 + elmtB2),
                                righttopVectorDrag = Math.sqrt(elmtR2 + elmtT2),
                                rightbottomVectorDrag = Math.sqrt(elmtR2 + elmtB2),
                                horizontalDeltaDrag = Math.abs(elmtL - elmtR) / 2,
                                verticalDeltaDrag = Math.abs(elmtT - elmtB) / 2,
                                leftVectorDrag = Math.sqrt(elmtL2 + verticalDeltaDrag ** 2),
                                topVectorDrag = Math.sqrt(elmtT2 + horizontalDeltaDrag ** 2),
                                rightVectorDrag = Math.sqrt(elmtR2 + verticalDeltaDrag ** 2),
                                bottomVectorDrag = Math.sqrt(elmtB2 + horizontalDeltaDrag ** 2);

                            // prevent selections while dragging
                            window.getSelection().removeAllRanges();

                            // trigger drag permanently while dragging
                            document.dispatchEvent(jspaneldrag);

                            // move elmt and apply axis option
                            if (!opts.axis || opts.axis === 'x') {
                                self.style.left = startLeft + (pmx - psx) / scaleFactor.x + startLeftCorrection + 'px'; // set new css left of elmt depending on opts.axis
                            }
                            if (!opts.axis || opts.axis === 'y') {
                                self.style.top = startTop + (pmy - psy) / scaleFactor.y + 'px'; // set new css top of elmt depending on opts.axis
                            }

                            // apply grid option
                            if (opts.grid) {
                                let grid = opts.grid,
                                    axis = opts.axis;
                                // formula rounds to the nearest multiple of grid
                                // https://www.webveteran.com/blog/web-coding/javascript-round-to-any-multiple-of-a-specific-number/
                                let x = grid[0] * Math.round((startLeft + (pmx - psx)) / grid[0]),
                                    y = grid[1] * Math.round((startTop + (pmy - psy)) / grid[1]);
                                if (!axis || axis === 'x') {
                                    self.style.left = `${x}px`;
                                }
                                if (!axis || axis === 'y') {
                                    self.style.top = `${y}px`;
                                }
                            }

                            // apply containment option
                            if (opts.containment || opts.containment === 0) {
                                let containment = opts.containment;
                                let maxLeft, maxTop;
                                // calc maxLeft and maxTop (minLeft and MinTop is equal to containment setting)
                                if (self.options.container === 'window') {
                                    maxLeft = window.innerWidth - parseFloat(dragStyles.width) - containment[1] - scrollbarwidths.y;
                                    maxTop = window.innerHeight - parseFloat(dragStyles.height) - containment[2] - scrollbarwidths.x;
                                } else {
                                    let xCorr = parseFloat(parentStyles.borderLeftWidth) + parseFloat(parentStyles.borderRightWidth),
                                        yCorr = parseFloat(parentStyles.borderTopWidth) + parseFloat(parentStyles.borderBottomWidth);
                                    maxLeft =
                                        parentRect.width / scaleFactor.x - parseFloat(dragStyles.width) - containment[1] - xCorr - scrollbarwidths.y;
                                    maxTop =
                                        parentRect.height / scaleFactor.y -
                                        parseFloat(dragStyles.height) -
                                        containment[2] -
                                        yCorr -
                                        scrollbarwidths.x;
                                }

                                if (parseFloat(self.style.left) <= containment[3]) {
                                    self.style.left = containment[3] + 'px';
                                }
                                if (parseFloat(self.style.top) <= containment[0]) {
                                    self.style.top = containment[0] + 'px';
                                }
                                if (parseFloat(self.style.left) >= maxLeft) {
                                    self.style.left = maxLeft + 'px';
                                }
                                if (parseFloat(self.style.top) >= maxTop) {
                                    self.style.top = maxTop + 'px';
                                }
                            }

                            // callback while dragging
                            if (opts.drag.length) {
                                let paneldata = {
                                    left: elmtL,
                                    top: elmtT,
                                    right: elmtR,
                                    bottom: elmtB,
                                    width: parseFloat(dragStyles.width),
                                    height: parseFloat(dragStyles.height),
                                };
                                jsPanel.processCallbacks(self, opts.drag, false, paneldata, e);
                            }

                            // apply snap options
                            if (opts.snap) {
                                let snapSens = opts.snap.sensitivity,
                                    topSensAreaLength = parent === document.body ? window.innerWidth / 8 : parentRect.width / 8,
                                    sideSensAreaLength = parent === document.body ? window.innerHeight / 8 : parentRect.height / 8;
                                self.snappableTo = false;
                                jsPanel.removeSnapAreas();

                                if (lefttopVectorDrag < snapSens) {
                                    if (opts.snap.snapLeftTop !== false) {
                                        if (!opts.snap.active || opts.snap.active === 'both') {
                                            self.snappableTo = 'left-top';
                                            jsPanel.createSnapArea(self, 'lt', snapSens);
                                        } else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
                                            if (overlaps.pointer.left > 0 && overlaps.pointer.top > 0) {
                                                self.snappableTo = 'left-top';
                                                jsPanel.createSnapArea(self, 'lt', snapSens);
                                            } else {
                                                self.snappableTo = false;
                                                jsPanel.removeSnapAreas();
                                            }
                                        }
                                    }
                                } else if (leftbottomVectorDrag < snapSens) {
                                    if (opts.snap.snapLeftBottom !== false) {
                                        if (!opts.snap.active || opts.snap.active === 'both') {
                                            self.snappableTo = 'left-bottom';
                                            jsPanel.createSnapArea(self, 'lb', snapSens);
                                        } else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
                                            if (overlaps.pointer.left > 0 && overlaps.pointer.bottom > 0) {
                                                self.snappableTo = 'left-bottom';
                                                jsPanel.createSnapArea(self, 'lb', snapSens);
                                            } else {
                                                self.snappableTo = false;
                                                jsPanel.removeSnapAreas();
                                            }
                                        }
                                    }
                                } else if (righttopVectorDrag < snapSens) {
                                    if (opts.snap.snapRightTop !== false) {
                                        if (!opts.snap.active || opts.snap.active === 'both') {
                                            self.snappableTo = 'right-top';
                                            jsPanel.createSnapArea(self, 'rt', snapSens);
                                        } else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
                                            if (overlaps.pointer.right > 0 && overlaps.pointer.top > 0) {
                                                self.snappableTo = 'right-top';
                                                jsPanel.createSnapArea(self, 'rt', snapSens);
                                            } else {
                                                self.snappableTo = false;
                                                jsPanel.removeSnapAreas();
                                            }
                                        }
                                    }
                                } else if (rightbottomVectorDrag < snapSens) {
                                    if (opts.snap.snapRightBottom !== false) {
                                        if (!opts.snap.active || opts.snap.active === 'both') {
                                            self.snappableTo = 'right-bottom';
                                            jsPanel.createSnapArea(self, 'rb', snapSens);
                                        } else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
                                            if (overlaps.pointer.right > 0 && overlaps.pointer.bottom > 0) {
                                                self.snappableTo = 'right-bottom';
                                                jsPanel.createSnapArea(self, 'rb', snapSens);
                                            } else {
                                                self.snappableTo = false;
                                                jsPanel.removeSnapAreas();
                                            }
                                        }
                                    }
                                } else if (elmtT < snapSens && topVectorDrag < topSensAreaLength) {
                                    if (opts.snap.snapCenterTop !== false) {
                                        if (!opts.snap.active || opts.snap.active === 'both') {
                                            self.snappableTo = 'center-top';
                                            jsPanel.createSnapArea(self, 'ct', snapSens);
                                        } else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
                                            if (overlaps.pointer.top > 0) {
                                                self.snappableTo = 'center-top';
                                                jsPanel.createSnapArea(self, 'ct', snapSens);
                                            } else {
                                                self.snappableTo = false;
                                                jsPanel.removeSnapAreas();
                                            }
                                        }
                                    }
                                } else if (elmtL < snapSens && leftVectorDrag < sideSensAreaLength) {
                                    if (opts.snap.snapLeftCenter !== false) {
                                        if (!opts.snap.active || opts.snap.active === 'both') {
                                            self.snappableTo = 'left-center';
                                            jsPanel.createSnapArea(self, 'lc', snapSens);
                                        } else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
                                            if (overlaps.pointer.left > 0) {
                                                self.snappableTo = 'left-center';
                                                jsPanel.createSnapArea(self, 'lc', snapSens);
                                            } else {
                                                self.snappableTo = false;
                                                jsPanel.removeSnapAreas();
                                            }
                                        }
                                    }
                                } else if (elmtR < snapSens && rightVectorDrag < sideSensAreaLength) {
                                    if (opts.snap.snapRightCenter !== false) {
                                        if (!opts.snap.active || opts.snap.active === 'both') {
                                            self.snappableTo = 'right-center';
                                            jsPanel.createSnapArea(self, 'rc', snapSens);
                                        } else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
                                            if (overlaps.pointer.right > 0) {
                                                self.snappableTo = 'right-center';
                                                jsPanel.createSnapArea(self, 'rc', snapSens);
                                            } else {
                                                self.snappableTo = false;
                                                jsPanel.removeSnapAreas();
                                            }
                                        }
                                    }
                                } else if (elmtB < snapSens && bottomVectorDrag < topSensAreaLength) {
                                    if (opts.snap.snapCenterBottom !== false) {
                                        if (!opts.snap.active || opts.snap.active === 'both') {
                                            self.snappableTo = 'center-bottom';
                                            jsPanel.createSnapArea(self, 'cb', snapSens);
                                        } else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
                                            if (overlaps.pointer.bottom > 0) {
                                                self.snappableTo = 'center-bottom';
                                                jsPanel.createSnapArea(self, 'cb', snapSens);
                                            } else {
                                                self.snappableTo = false;
                                                jsPanel.removeSnapAreas();
                                            }
                                        }
                                    }
                                }
                            }

                            // opts.drop
                            if (opts.drop && opts.drop.dropZones) {
                                // IE doesn't offer document.elementsFromPoint() but document.msElementsFromPoint()
                                let elementsFromPoint = jsPanel.isIE ? 'msElementsFromPoint' : 'elementsFromPoint';
                                let elementsFrom = document[elementsFromPoint](e.clientX, e.clientY);
                                // document.msElementsFromPoint() returns a nodeList -> convert to array
                                if (!Array.isArray(elementsFrom)) {
                                    elementsFrom = Array.prototype.slice.call(elementsFrom);
                                }
                                opts.drop.dropZones.forEach(zone => {
                                    // Array.prototype.includes() needs polyfill in IE
                                    if (elementsFrom.includes(zone)) {
                                        self.droppableTo = zone;
                                    }
                                });
                                // do not include following if statement in this.options.dragit.drop.dropZones.forEach !!!!
                                if (!elementsFrom.includes(self.droppableTo)) {
                                    self.droppableTo = false;
                                }
                            }
                        };

                        jsPanel.pointermove.forEach(e => document.addEventListener(e, dragElmt));

                        // remove drag handler when mouse leaves browser window (mouseleave doesn't work)
                        window.addEventListener('mouseout', windowListener, false);
                    });
                });

                jsPanel.pointerup.forEach(event => {
                    document.addEventListener(event, pointerUpHandlerDragit);
                    window.removeEventListener('mouseout', windowListener);
                });

                // dragit is initialized - now disable if set
                if (options.disable) {
                    handle.style.pointerEvents = 'none';
                }
            });

            return self;
        };
        self.dragit = string => {
            const dragitOptions = Object.assign({}, jsPanel.defaults.dragit, options.dragit),
                handles = self.querySelectorAll(dragitOptions.handles);
            if (string === 'disable') {
                handles.forEach(handle => handle.style.pointerEvents = 'none');
            } else {
                handles.forEach(handle => handle.style.pointerEvents = 'auto');
            }
            return self;
        };

        self.sizeit = (options = {}) => {
            const jspanelresizestart = new CustomEvent('jspanelresizestart', { detail: self.id }),
                jspanelresize = new CustomEvent('jspanelresize', { detail: self.id }),
                jspanelresizestop = new CustomEvent('jspanelresizestop', { detail: self.id });
            // make panel available as event object property 'panel'
            [jspanelresizestart, jspanelresize, jspanelresizestop].forEach(evt => evt.panel = self);
            let opts = {},
                resizePanel,
                resizestarted,
                w,
                h,
                startWidth,
                startHeight;

            opts.handles = options.handles || jsPanel.defaults.resizeit.handles;
            opts.handles.split(',').forEach(item => {
                const node = document.createElement('DIV');
                node.className = `jsPanel-resizeit-handle jsPanel-resizeit-${item.trim()}`;
                //node.style.zIndex = 90;
                self.append(node);
            });
            // cache option aspectRatio of original resizeit configuration (is restored on pointerup)
            let cachedOptionAspectRatio = options.aspectRatio ? options.aspectRatio : false;

            function windowListener(e) {
                if (e.relatedTarget === null) {
                    jsPanel.pointermove.forEach(evt => document.removeEventListener(evt, resizePanel, false));
                }
            }

            function pointerUpHandlerResizeit(e) {
                jsPanel.pointermove.forEach(evt => document.removeEventListener(evt, resizePanel, false));
                if (e.target.classList && e.target.classList.contains('jsPanel-resizeit-handle')) {
                    let isLeftChange,
                        isTopChange,
                        cl = e.target.className;
                    if (cl.match(/jsPanel-resizeit-nw|jsPanel-resizeit-w|jsPanel-resizeit-sw/i)) {
                        isLeftChange = true;
                    }
                    if (cl.match(/jsPanel-resizeit-nw|jsPanel-resizeit-n|jsPanel-resizeit-ne/i)) {
                        isTopChange = true;
                    }

                    // snap panel to grid (doesn't work that well if inside function resizePanel)
                    if (opts.grid && Array.isArray(opts.grid)) {
                        if (opts.grid.length === 1) {
                            opts.grid[1] = opts.grid[0];
                        }
                        const cw = parseFloat(self.style.width),
                            ch = parseFloat(self.style.height),
                            modW = cw % opts.grid[0],
                            modH = ch % opts.grid[1],
                            cx = parseFloat(self.style.left),
                            cy = parseFloat(self.style.top),
                            modX = cx % opts.grid[0],
                            modY = cy % opts.grid[1];

                        if (modW < opts.grid[0] / 2) {
                            self.style.width = cw - modW + 'px';
                        } else {
                            self.style.width = cw + (opts.grid[0] - modW) + 'px';
                        }
                        if (modH < opts.grid[1] / 2) {
                            self.style.height = ch - modH + 'px';
                        } else {
                            self.style.height = ch + (opts.grid[1] - modH) + 'px';
                        }

                        if (isLeftChange) {
                            if (modX < opts.grid[0] / 2) {
                                self.style.left = cx - modX + 'px';
                            } else {
                                self.style.left = cx + (opts.grid[0] - modX) + 'px';
                            }
                        }
                        if (isTopChange) {
                            if (modY < opts.grid[1] / 2) {
                                self.style.top = cy - modY + 'px';
                            } else {
                                self.style.top = cy + (opts.grid[1] - modY) + 'px';
                            }
                        }
                    }
                }
                if (resizestarted) {
                    self.content.style.pointerEvents = 'inherit';
                    resizestarted = undefined;
                    self.saveCurrentDimensions();
                    self.saveCurrentPosition();
                    self.calcSizeFactors();
                    let smallifyBtn = self.controlbar.querySelector('.jsPanel-btn-smallify');
                    let elmtRect = self.getBoundingClientRect();
                    if (smallifyBtn && elmtRect.height > startHeight + 5) {
                        smallifyBtn.style.transform = 'rotate(0deg)';
                    }
                    document.dispatchEvent(jspanelresizestop);

                    if (opts.stop.length) {
                        let stopStyles = window.getComputedStyle(self),
                            paneldata = {
                                left: parseFloat(stopStyles.left),
                                top: parseFloat(stopStyles.top),
                                width: parseFloat(stopStyles.width),
                                height: parseFloat(stopStyles.height),
                            };
                        jsPanel.processCallbacks(self, opts.stop, false, paneldata, e);
                    }
                }
                self.content.style.pointerEvents = 'inherit';
                // restore other panel's css pointer-events
                document.querySelectorAll('iframe').forEach(frame => frame.style.pointerEvents = 'auto');
                // restore option aspectRatio to original configuration
                opts.aspectRatio = cachedOptionAspectRatio;
                document.removeEventListener(e, pointerUpHandlerResizeit);
            }

            self.querySelectorAll('.jsPanel-resizeit-handle').forEach(handle => {
                handle.style.touchAction = 'none';

                jsPanel.pointerdown.forEach(event => {
                    handle.addEventListener(event, e => {
                        // prevent window scroll while resizing elmt
                        e.preventDefault();
                        e.stopPropagation();
                        // disable resizing for all mouse buttons but left
                        if (e.button && e.button > 0) {
                            return false;
                        }
                        // factor is needed only for the modifier key Shift feature
                        let factor = 1;
                        // setup and normalize resizeit options
                        opts = Object.assign({}, jsPanel.defaults.resizeit, options);
                        if (opts.containment || opts.containment === 0) {
                            opts.containment = jsPanel.pOcontainment(opts.containment);
                        }
                        // legacy line: aspectRatio should be either 'panel' or 'content', not just true
                        if (opts.aspectRatio && opts.aspectRatio === true) {
                            opts.aspectRatio = 'panel';
                        }
                        // set aspectRatio according to modifier key
                        if (jsPanel.modifier) {
                            let modifier = jsPanel.modifier;
                            if (modifier.altKey) {
                                opts.aspectRatio = 'content';
                            } else if (modifier.ctrlKey) {
                                opts.aspectRatio = 'panel';
                            } else if (modifier.shiftKey) {
                                opts.aspectRatio = false;
                                factor = 2; // does work only with 2 as value
                            }
                        }

                        // noinspection JSUnresolvedFunction
                        let maxWidth = typeof opts.maxWidth === 'function' ? opts.maxWidth() : opts.maxWidth || 10000,
                            maxHeight = typeof opts.maxHeight === 'function' ? opts.maxHeight() : opts.maxHeight || 10000,
                            minWidth = typeof opts.minWidth === 'function' ? opts.minWidth() : opts.minWidth,
                            minHeight = typeof opts.minHeight === 'function' ? opts.minHeight() : opts.minHeight;

                        self.content.style.pointerEvents = 'none';
                        // prevents iframes in other panel from interfering with resize action of dragged panel
                        document.querySelectorAll('iframe').forEach(frame => frame.style.pointerEvents = 'none');

                        // noinspection JSUnresolvedVariable
                        const elmtParent = self.parentElement,
                            elmtParentTagName = elmtParent.tagName.toLowerCase(),
                            elmtRect = self.getBoundingClientRect(),
                            elmtParentRect = elmtParent.getBoundingClientRect(),
                            elmtParentStyles = window.getComputedStyle(elmtParent, null),
                            elmtParentBLW = parseInt(elmtParentStyles.borderLeftWidth, 10),
                            elmtParentBTW = parseInt(elmtParentStyles.borderTopWidth, 10),
                            elmtParentPosition = elmtParentStyles.getPropertyValue('position'),
                            startX = (e.clientX || e.clientX === 0) || e.touches[0].clientX,
                            startY = (e.clientY || e.clientY === 0) || e.touches[0].clientY,
                            startRatio = startX / startY,
                            resizeHandleClassList = e.target.classList,
                            scaleFactor = self.getScaleFactor(),
                            aspectRatio = elmtRect.width / elmtRect.height,
                            elmtContentRect = self.content.getBoundingClientRect(),
                            aspectRatioContent = elmtContentRect.width / elmtContentRect.height,
                            hdrHeight = self.header.getBoundingClientRect().height, // needed in aspectRatio
                            ftrHeight = self.footer.getBoundingClientRect().height || 0; // needed in aspectRatio
                        let startLeft = elmtRect.left,
                            startTop = elmtRect.top,
                            maxWidthEast = 10000,
                            maxWidthWest = 10000,
                            maxHeightSouth = 10000,
                            maxHeightNorth = 10000;

                        startWidth = elmtRect.width;
                        startHeight = elmtRect.height;

                        if (elmtParentTagName !== 'body') {
                            startLeft = elmtRect.left - elmtParentRect.left + elmtParent.scrollLeft;
                            startTop = elmtRect.top - elmtParentRect.top + elmtParent.scrollTop;
                        }

                        // calc min/max left/top values if containment is set - code from jsDraggable
                        if (elmtParentTagName === 'body' && opts.containment) {
                            maxWidthEast = document.documentElement.clientWidth - elmtRect.left;
                            maxHeightSouth = document.documentElement.clientHeight - elmtRect.top;
                            maxWidthWest = elmtRect.width + elmtRect.left;
                            maxHeightNorth = elmtRect.height + elmtRect.top;
                        } else {
                            // if panel is NOT in body
                            if (opts.containment) {
                                if (elmtParentPosition === 'static') {
                                    maxWidthEast = elmtParentRect.width - elmtRect.left + elmtParentBLW;
                                    maxHeightSouth = elmtParentRect.height + elmtParentRect.top - elmtRect.top + elmtParentBTW;
                                    maxWidthWest = elmtRect.width + (elmtRect.left - elmtParentRect.left) - elmtParentBLW;
                                    maxHeightNorth = elmtRect.height + (elmtRect.top - elmtParentRect.top) - elmtParentBTW;
                                } else {
                                    maxWidthEast = elmtParent.clientWidth - (elmtRect.left - elmtParentRect.left) / scaleFactor.x + elmtParentBLW;
                                    maxHeightSouth = elmtParent.clientHeight - (elmtRect.top - elmtParentRect.top) / scaleFactor.y + elmtParentBTW;
                                    maxWidthWest = (elmtRect.width + elmtRect.left - elmtParentRect.left) / scaleFactor.x - elmtParentBLW;
                                    maxHeightNorth = self.clientHeight + (elmtRect.top - elmtParentRect.top) / scaleFactor.y - elmtParentBTW;
                                }
                            }
                        }
                        // if original opts.containment is an array
                        if (opts.containment) {
                            maxWidthWest -= opts.containment[3];
                            maxHeightNorth -= opts.containment[0];
                            maxWidthEast -= opts.containment[1];
                            maxHeightSouth -= opts.containment[2];
                        }

                        // calculate corrections for rotated panels
                        const computedStyle = window.getComputedStyle(self),
                            wDif = parseFloat(computedStyle.width) - elmtRect.width,
                            hDif = parseFloat(computedStyle.height) - elmtRect.height;
                        let xDif = parseFloat(computedStyle.left) - elmtRect.left,
                            yDif = parseFloat(computedStyle.top) - elmtRect.top;
                        if (elmtParent !== document.body) {
                            xDif += elmtParentRect.left;
                            yDif += elmtParentRect.top;
                        }
                        // used in aspectRatio code
                        let borderTopWidth = parseInt(computedStyle.borderTopWidth, 10),
                            borderRightWidth = parseInt(computedStyle.borderRightWidth, 10),
                            borderBottomWidth = parseInt(computedStyle.borderBottomWidth, 10),
                            borderLeftWidth = parseInt(computedStyle.borderLeftWidth, 10);

                        resizePanel = evt => {
                            evt.preventDefault();
                            // trigger resizestarted only once per resize
                            if (!resizestarted) {
                                document.dispatchEvent(jspanelresizestart);
                                if (opts.start.length) {
                                    jsPanel.processCallbacks(
                                        self,
                                        opts.start,
                                        false,
                                        {
                                            width: startWidth,
                                            height: startHeight,
                                            left: startLeft,
                                            top: startTop,
                                        },
                                        evt
                                    );
                                }
                                self.front();

                                // if panel is maximized on resize start set status to normalized and swap maximize/normalize buttons
                                if (self.status === 'maximized') {
                                    self.status = 'normalized';
                                    if (self.controlbar.querySelector('.jsPanel-btn-maximize')) {
                                        self.setControlStatus('maximize', 'show');
                                    }
                                    if (self.controlbar.querySelector('.jsPanel-btn-normalize')) {
                                        self.setControlStatus('normalize', 'hide');
                                    }
                                }
                                if (elmtRect.height > startHeight + 5) {
                                    self.status = 'normalized';
                                    self.setControls(['.jsPanel-btn-normalize']);
                                }
                            }
                            resizestarted = 1;
                            // trigger resize permanently while resizing
                            document.dispatchEvent(jspanelresize);

                            // possibly updated while resizing
                            let eventX = evt.touches ? evt.touches[0].clientX : evt.clientX,
                                eventY = evt.touches ? evt.touches[0].clientY : evt.clientY,
                                overlaps;

                            if (resizeHandleClassList.contains('jsPanel-resizeit-e')) {
                                //w = startWidth + (eventX - startX) / scaleFactor.x + wDif;
                                w = startWidth + ((eventX - startX) * factor) / scaleFactor.x + wDif; // needs left adjust, for width and height adjust factor may be either 1 (no adjust) or 2
                                if (w >= maxWidthEast) {
                                    w = maxWidthEast;
                                }
                                if (w >= maxWidth) {
                                    w = maxWidth;
                                }
                                if (w <= minWidth) {
                                    w = minWidth;
                                }
                                self.style.width = w + 'px';
                                if (factor === 2) {
                                    // factor works only with value of 2 when adjusting left or top
                                    self.style.left = startLeft - (eventX - startX) + 'px';
                                }
                                if (opts.aspectRatio === 'content') {
                                    // if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
                                    self.style.height =
                                        (w - borderRightWidth - borderLeftWidth) / aspectRatioContent +
                                        hdrHeight +
                                        ftrHeight +
                                        borderTopWidth +
                                        borderBottomWidth +
                                        'px';
                                    if (opts.containment) {
                                        overlaps = self.overlaps(elmtParent);
                                        if (overlaps.bottom <= opts.containment[2]) {
                                            self.style.height = maxHeightSouth + 'px';
                                            self.style.width = maxHeightSouth * aspectRatioContent + 'px';
                                        }
                                    }
                                } else if (opts.aspectRatio === 'panel') {
                                    // otherwise the complete panel maintains its aspect ratio
                                    self.style.height = w / aspectRatio + 'px';
                                    if (opts.containment) {
                                        overlaps = self.overlaps(elmtParent);
                                        if (overlaps.bottom <= opts.containment[2]) {
                                            self.style.height = maxHeightSouth + 'px';
                                            self.style.width = maxHeightSouth * aspectRatio + 'px';
                                        }
                                    }
                                }
                            } else if (resizeHandleClassList.contains('jsPanel-resizeit-s')) {
                                //h = startHeight + (eventY - startY) / scaleFactor.y + hDif;
                                h = startHeight + ((eventY - startY) * factor) / scaleFactor.y + hDif; // needs top adjust
                                if (h >= maxHeightSouth) {
                                    h = maxHeightSouth;
                                }
                                if (h >= maxHeight) {
                                    h = maxHeight;
                                }
                                if (h <= minHeight) {
                                    h = minHeight;
                                }
                                self.style.height = h + 'px';
                                if (factor === 2) {
                                    self.style.top = startTop - (eventY - startY) + 'px';
                                }
                                if (opts.aspectRatio === 'content') {
                                    // if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
                                    self.style.width =
                                        (h - hdrHeight - ftrHeight - borderTopWidth - borderBottomWidth) * aspectRatioContent +
                                        borderTopWidth +
                                        borderBottomWidth +
                                        'px';
                                    if (opts.containment) {
                                        overlaps = self.overlaps(elmtParent);
                                        if (overlaps.right <= opts.containment[1]) {
                                            self.style.width = maxWidthEast + 'px';
                                            self.style.height = maxWidthEast / aspectRatioContent + 'px';
                                        }
                                    }
                                } else if (opts.aspectRatio === 'panel') {
                                    // otherwise the complete panel maintains its aspect ratio
                                    self.style.width = h * aspectRatio + 'px';
                                    if (opts.containment) {
                                        overlaps = self.overlaps(elmtParent);
                                        if (overlaps.right <= opts.containment[1]) {
                                            self.style.width = maxWidthEast + 'px';
                                            self.style.height = maxWidthEast / aspectRatio + 'px';
                                        }
                                    }
                                }
                            } else if (resizeHandleClassList.contains('jsPanel-resizeit-w')) {
                                //w = startWidth + (startX - eventX) / scaleFactor.x + wDif;
                                w = startWidth + ((startX - eventX) * factor) / scaleFactor.x + wDif; // doesn't need left adjust
                                if (w <= maxWidth && w >= minWidth && w <= maxWidthWest) {
                                    self.style.left = startLeft + (eventX - startX) / scaleFactor.x + xDif + 'px';
                                }
                                if (w >= maxWidthWest) {
                                    w = maxWidthWest;
                                }
                                if (w >= maxWidth) {
                                    w = maxWidth;
                                }
                                if (w <= minWidth) {
                                    w = minWidth;
                                }
                                self.style.width = w + 'px';
                                if (opts.aspectRatio === 'content') {
                                    // if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
                                    self.style.height =
                                        (w - borderRightWidth - borderLeftWidth) / aspectRatioContent +
                                        hdrHeight +
                                        ftrHeight +
                                        borderTopWidth +
                                        borderBottomWidth +
                                        'px';
                                    if (opts.containment) {
                                        overlaps = self.overlaps(elmtParent);
                                        if (overlaps.bottom <= opts.containment[2]) {
                                            self.style.height = maxHeightSouth + 'px';
                                            self.style.width = maxHeightSouth * aspectRatioContent + 'px';
                                        }
                                    }
                                } else if (opts.aspectRatio === 'panel') {
                                    // otherwise the complete panel maintains its aspect ratio
                                    self.style.height = w / aspectRatio + 'px';
                                    if (opts.containment) {
                                        overlaps = self.overlaps(elmtParent);
                                        if (overlaps.bottom <= opts.containment[2]) {
                                            self.style.height = maxHeightSouth + 'px';
                                            self.style.width = maxHeightSouth * aspectRatio + 'px';
                                        }
                                    }
                                }
                            } else if (resizeHandleClassList.contains('jsPanel-resizeit-n')) {
                                //h = startHeight + (startY - eventY) / scaleFactor.y + hDif;
                                h = startHeight + ((startY - eventY) * factor) / scaleFactor.y + hDif; // doesn't need top adjust
                                if (h <= maxHeight && h >= minHeight && h <= maxHeightNorth) {
                                    self.style.top = startTop + (eventY - startY) / scaleFactor.y + yDif + 'px';
                                }
                                if (h >= maxHeightNorth) {
                                    h = maxHeightNorth;
                                }
                                if (h >= maxHeight) {
                                    h = maxHeight;
                                }
                                if (h <= minHeight) {
                                    h = minHeight;
                                }
                                self.style.height = h + 'px';
                                if (opts.aspectRatio === 'content') {
                                    // if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
                                    self.style.width =
                                        (h - hdrHeight - ftrHeight - borderTopWidth - borderBottomWidth) * aspectRatioContent +
                                        borderTopWidth +
                                        borderBottomWidth +
                                        'px';
                                    if (opts.containment) {
                                        overlaps = self.overlaps(elmtParent);
                                        if (overlaps.right <= opts.containment[1]) {
                                            self.style.width = maxWidthEast + 'px';
                                            self.style.height = maxWidthEast / aspectRatioContent + 'px';
                                        }
                                    }
                                } else if (opts.aspectRatio === 'panel') {
                                    // otherwise the complete panel maintains its aspect ratio
                                    self.style.width = h * aspectRatio + 'px';
                                    if (opts.containment) {
                                        overlaps = self.overlaps(elmtParent);
                                        if (overlaps.right <= opts.containment[1]) {
                                            self.style.width = maxWidthEast + 'px';
                                            self.style.height = maxWidthEast / aspectRatio + 'px';
                                        }
                                    }
                                }
                            } else if (resizeHandleClassList.contains('jsPanel-resizeit-se')) {
                                //w = startWidth + (eventX - startX) / scaleFactor.x + wDif;
                                w = startWidth + ((eventX - startX) * factor) / scaleFactor.x + wDif; // needs left adjust
                                if (w >= maxWidthEast) {
                                    w = maxWidthEast;
                                }
                                if (w >= maxWidth) {
                                    w = maxWidth;
                                }
                                if (w <= minWidth) {
                                    w = minWidth;
                                }
                                self.style.width = w + 'px';
                                if (factor === 2) {
                                    self.style.left = startLeft - (eventX - startX) + 'px';
                                }
                                if (opts.aspectRatio) {
                                    self.style.height = w / aspectRatio + 'px';
                                }

                                //h = startHeight + (eventY - startY) / scaleFactor.y + hDif;
                                h = startHeight + ((eventY - startY) * factor) / scaleFactor.y + hDif; // needs top adjust
                                if (h >= maxHeightSouth) {
                                    h = maxHeightSouth;
                                }
                                if (h >= maxHeight) {
                                    h = maxHeight;
                                }
                                if (h <= minHeight) {
                                    h = minHeight;
                                }
                                self.style.height = h + 'px';
                                if (factor === 2) {
                                    self.style.top = startTop - (eventY - startY) + 'px';
                                }
                                if (opts.aspectRatio === 'content') {
                                    // if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
                                    self.style.width =
                                        (h - hdrHeight - ftrHeight - borderTopWidth - borderBottomWidth) * aspectRatioContent +
                                        borderTopWidth +
                                        borderBottomWidth +
                                        'px';
                                    if (opts.containment) {
                                        overlaps = self.overlaps(elmtParent);
                                        if (overlaps.right <= opts.containment[1]) {
                                            self.style.width = maxWidthEast + 'px';
                                            self.style.height = maxWidthEast / aspectRatioContent + 'px';
                                        }
                                    }
                                } else if (opts.aspectRatio === 'panel') {
                                    // otherwise the complete panel maintains its aspect ratio
                                    self.style.width = h * aspectRatio + 'px';
                                    if (opts.containment) {
                                        overlaps = self.overlaps(elmtParent);
                                        if (overlaps.right <= opts.containment[1]) {
                                            self.style.width = maxWidthEast + 'px';
                                            self.style.height = maxWidthEast / aspectRatio + 'px';
                                        }
                                    }
                                }
                            } else if (resizeHandleClassList.contains('jsPanel-resizeit-sw')) {
                                //h = startHeight + (eventY - startY) / scaleFactor.y + hDif;
                                h = startHeight + ((eventY - startY) * factor) / scaleFactor.y + hDif; // needs top adjust
                                if (h >= maxHeightSouth) {
                                    h = maxHeightSouth;
                                }
                                if (h >= maxHeight) {
                                    h = maxHeight;
                                }
                                if (h <= minHeight) {
                                    h = minHeight;
                                }
                                self.style.height = h + 'px';
                                if (factor === 2) {
                                    self.style.top = startTop - (eventY - startY) + 'px';
                                }
                                if (opts.aspectRatio) {
                                    self.style.width = h * aspectRatio + 'px';
                                }

                                //w = startWidth + (startX - eventX) / scaleFactor.x + wDif;
                                w = startWidth + ((startX - eventX) * factor) / scaleFactor.x + wDif; // doesn't need left adjust
                                if (w <= maxWidth && w >= minWidth && w <= maxWidthWest) {
                                    self.style.left = startLeft + (eventX - startX) / scaleFactor.x + xDif + 'px';
                                }
                                if (w >= maxWidthWest) {
                                    w = maxWidthWest;
                                }
                                if (w >= maxWidth) {
                                    w = maxWidth;
                                }
                                if (w <= minWidth) {
                                    w = minWidth;
                                }
                                self.style.width = w + 'px';
                                if (opts.aspectRatio === 'content') {
                                    // if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
                                    self.style.height =
                                        (w - borderRightWidth - borderLeftWidth) / aspectRatioContent +
                                        hdrHeight +
                                        ftrHeight +
                                        borderTopWidth +
                                        borderBottomWidth +
                                        'px';
                                    if (opts.containment) {
                                        overlaps = self.overlaps(elmtParent);
                                        if (overlaps.bottom <= opts.containment[2]) {
                                            self.style.height = maxHeightSouth + 'px';
                                            self.style.width = maxHeightSouth * aspectRatioContent + 'px';
                                        }
                                    }
                                } else if (opts.aspectRatio === 'panel') {
                                    // otherwise the complete panel maintains its aspect ratio
                                    self.style.height = w / aspectRatio + 'px';
                                    if (opts.containment) {
                                        overlaps = self.overlaps(elmtParent);
                                        if (overlaps.bottom <= opts.containment[2]) {
                                            self.style.height = maxHeightSouth + 'px';
                                            self.style.width = maxHeightSouth * aspectRatio + 'px';
                                        }
                                    }
                                }
                            } else if (resizeHandleClassList.contains('jsPanel-resizeit-ne')) {
                                //w = startWidth + (eventX - startX) / scaleFactor.x + wDif;
                                w = startWidth + ((eventX - startX) * factor) / scaleFactor.x + wDif; // needs left adjust
                                if (w >= maxWidthEast) {
                                    w = maxWidthEast;
                                }
                                if (w >= maxWidth) {
                                    w = maxWidth;
                                }
                                if (w <= minWidth) {
                                    w = minWidth;
                                }
                                self.style.width = w + 'px';
                                if (factor === 2) {
                                    self.style.left = startLeft - (eventX - startX) + 'px';
                                }
                                if (opts.aspectRatio) {
                                    self.style.height = w / aspectRatio + 'px';
                                }

                                h = startHeight + ((startY - eventY) * factor) / scaleFactor.y + hDif; // doesn't need top adjust
                                if (h <= maxHeight && h >= minHeight && h <= maxHeightNorth) {
                                    self.style.top = startTop + (eventY - startY) / scaleFactor.y + yDif + 'px';
                                }
                                if (h >= maxHeightNorth) {
                                    h = maxHeightNorth;
                                }
                                if (h >= maxHeight) {
                                    h = maxHeight;
                                }
                                if (h <= minHeight) {
                                    h = minHeight;
                                }
                                self.style.height = h + 'px';
                                if (opts.aspectRatio === 'content') {
                                    // if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
                                    self.style.width =
                                        (h - hdrHeight - ftrHeight - borderTopWidth - borderBottomWidth) * aspectRatioContent +
                                        borderTopWidth +
                                        borderBottomWidth +
                                        'px';
                                    if (opts.containment) {
                                        overlaps = self.overlaps(elmtParent);
                                        if (overlaps.right <= opts.containment[1]) {
                                            self.style.width = maxWidthEast + 'px';
                                            self.style.height = maxWidthEast / aspectRatioContent + 'px';
                                        }
                                    }
                                } else if (opts.aspectRatio === 'panel') {
                                    // otherwise the complete panel maintains its aspect ratio
                                    self.style.width = h * aspectRatio + 'px';
                                    if (opts.containment) {
                                        overlaps = self.overlaps(elmtParent);
                                        if (overlaps.right <= opts.containment[1]) {
                                            self.style.width = maxWidthEast + 'px';
                                            self.style.height = maxWidthEast / aspectRatio + 'px';
                                        }
                                    }
                                }
                            } else if (resizeHandleClassList.contains('jsPanel-resizeit-nw')) {
                                if (opts.aspectRatio && resizeHandleClassList.contains('jsPanel-resizeit-nw')) {
                                    eventX = eventY * startRatio;
                                    eventY = eventX / startRatio;
                                }
                                //w = startWidth + (startX - eventX) / scaleFactor.x + wDif;
                                w = startWidth + ((startX - eventX) * factor) / scaleFactor.x + wDif; // doesn't need left adjust
                                if (w <= maxWidth && w >= minWidth && w <= maxWidthWest) {
                                    self.style.left = startLeft + (eventX - startX) / scaleFactor.x + xDif + 'px';
                                }
                                if (w >= maxWidthWest) {
                                    w = maxWidthWest;
                                }
                                if (w >= maxWidth) {
                                    w = maxWidth;
                                }
                                if (w <= minWidth) {
                                    w = minWidth;
                                }
                                self.style.width = w + 'px';
                                if (opts.aspectRatio) {
                                    self.style.height = w / aspectRatio + 'px';
                                }

                                h = startHeight + ((startY - eventY) * factor) / scaleFactor.y + hDif; // doesn't need top adjust
                                if (h <= maxHeight && h >= minHeight && h <= maxHeightNorth) {
                                    self.style.top = startTop + (eventY - startY) / scaleFactor.y + yDif + 'px';
                                }
                                if (h >= maxHeightNorth) {
                                    h = maxHeightNorth;
                                }
                                if (h >= maxHeight) {
                                    h = maxHeight;
                                }
                                if (h <= minHeight) {
                                    h = minHeight;
                                }
                                self.style.height = h + 'px';
                                if (opts.aspectRatio === 'content') {
                                    // if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
                                    self.style.width =
                                        (h - hdrHeight - ftrHeight - borderTopWidth - borderBottomWidth) * aspectRatioContent +
                                        borderTopWidth +
                                        borderBottomWidth +
                                        'px';
                                } else if (opts.aspectRatio === 'panel') {
                                    // otherwise the complete panel maintains its aspect ratio
                                    self.style.width = h * aspectRatio + 'px';
                                }
                            }

                            window.getSelection().removeAllRanges();

                            // get current position and size values while resizing
                            const styles = window.getComputedStyle(self),
                                values = {
                                    left: parseFloat(styles.left),
                                    top: parseFloat(styles.top),
                                    right: parseFloat(styles.right),
                                    bottom: parseFloat(styles.bottom),
                                    width: parseFloat(styles.width),
                                    height: parseFloat(styles.height),
                                };

                            // callback while resizing
                            if (opts.resize.length) {
                                jsPanel.processCallbacks(self, opts.resize, false, values, evt);
                            }
                        };

                        jsPanel.pointermove.forEach(event => document.addEventListener(event, resizePanel, false));

                        // remove resize handler when mouse leaves browser window (mouseleave doesn't work)
                        window.addEventListener('mouseout', windowListener, false);
                    });
                });

                jsPanel.pointerup.forEach(function (event) {
                    document.addEventListener(event, pointerUpHandlerResizeit);
                    window.removeEventListener('mouseout', windowListener);
                });

                // resizeit is initialized - now disable if set
                if (options.disable) {
                    handle.style.pointerEvents = 'none';
                }
            });

            return self;
        };
        self.resizeit = string => {
            const handles = self.querySelectorAll('.jsPanel-resizeit-handle');
            if (string === 'disable') {
                handles.forEach(handle => handle.style.pointerEvents = 'none');
            } else {
                handles.forEach(handle => handle.style.pointerEvents = 'auto');
            }
            return self;
        };

        self.getScaleFactor = () => {
            const rect = self.getBoundingClientRect();
            return {
                x: rect.width / self.offsetWidth,
                y: rect.height / self.offsetHeight,
            };
        };
        self.calcSizeFactors = () => {
            const styles = window.getComputedStyle(self);
            if (options.container === 'window') {
                self.hf = parseFloat(styles.left) / (window.innerWidth - parseFloat(styles.width));
                self.vf = parseFloat(styles.top) / (window.innerHeight - parseFloat(styles.height));
            } else {
                if (self.parentElement) {
                    let parentStyles = self.parentElement.getBoundingClientRect();
                    self.hf = parseFloat(styles.left) / (parentStyles.width - parseFloat(styles.width));
                    self.vf = parseFloat(styles.top) / (parentStyles.height - parseFloat(styles.height));
                }
            }
        };
        self.saveCurrentDimensions = () => {
            const normData = window.getComputedStyle(self);
            self.currentData.width = normData.width;
            self.currentData.height = normData.height;
        };
        self.saveCurrentPosition = () => {
            const normData = window.getComputedStyle(self);
            self.currentData.left = normData.left;
            self.currentData.top = normData.top;
        };
        self.reposition = (...params) => {
            let pos = options.position,
                updateCache = true,
                callback;
            params.forEach(value => {
                if (typeof value === 'string' || typeof value === 'object') {
                    pos = value;
                } else if (typeof value === 'boolean') {
                    updateCache = value;
                } else if (typeof value === 'function') {
                    callback = value;
                }
            });

            jsPanel.position(self, pos);

            // check whether self has docked panels -> reposition docked panels as well
            if (self.slaves && self.slaves.size > 0) {
                self.slaves.forEach(slave => slave.reposition());
            }

            if (updateCache) {
                self.saveCurrentPosition();
            }
            if (callback) {
                callback.call(self, self);
            }
            return self;
        };
        self.repositionOnSnap = pos => {
            let offsetX = '0',
                offsetY = '0',
                margins = jsPanel.pOcontainment(options.dragit.containment);
            // calculate offsets
            if (options.dragit.snap.containment) {
                switch (pos) {
                    case 'left-top':
                        offsetX = margins[3];
                        offsetY = margins[0];
                        break;
                    case 'right-top':
                        offsetX = -margins[1];
                        offsetY = margins[0];
                        break;
                    case 'right-bottom':
                        offsetX = -margins[1];
                        offsetY = -margins[2];
                        break;
                    case 'left-bottom':
                        offsetX = margins[3];
                        offsetY = -margins[2];
                        break;
                    case 'center-top':
                        offsetX = margins[3] / 2 - margins[1] / 2;
                        offsetY = margins[0];
                        break;
                    case 'center-bottom':
                        offsetX = margins[3] / 2 - margins[1] / 2;
                        offsetY = -margins[2];
                        break;
                    case 'left-center':
                        offsetX = margins[3];
                        offsetY = margins[0] / 2 - margins[2] / 2;
                        break;
                    case 'right-center':
                        offsetX = -margins[1];
                        offsetY = margins[0] / 2 - margins[2] / 2;
                        break;
                }
            }
            /* jsPanel.position(self, `${pos} ${offsetX} ${offsetY}`);
               For some reason I could not find the line above does not work (pos and offsets in one string), but only when
               center-bottom is used with different settings for left/right margin.
            */
            jsPanel.position(self, pos);
            jsPanel.setStyles(self, {
                left: `calc(${self.style.left} + ${offsetX}px)`,
                top: `calc(${self.style.top} + ${offsetY}px)`,
            });
        };
        self.overlaps = (reference, elmtBox, event) => {
            let panelRect = self.getBoundingClientRect(),
                parentStyle = getComputedStyle(self.parentElement),
                scaleFactor = self.getScaleFactor(),
                parentBorderWidth = { top: 0, right: 0, bottom: 0, left: 0 },
                referenceRect,
                evtX = 0,
                evtY = 0,
                evtXparent = 0,
                evtYparent = 0;

            if (self.options.container !== 'window' && elmtBox === 'paddingbox') {
                parentBorderWidth.top = parseInt(parentStyle.borderTopWidth, 10) * scaleFactor.y;
                parentBorderWidth.right = parseInt(parentStyle.borderRightWidth, 10) * scaleFactor.x;
                parentBorderWidth.bottom = parseInt(parentStyle.borderBottomWidth, 10) * scaleFactor.y;
                parentBorderWidth.left = parseInt(parentStyle.borderLeftWidth, 10) * scaleFactor.x;
            }

            if (typeof reference === 'string') {
                if (reference === 'window') {
                    referenceRect = {
                        left: 0,
                        top: 0,
                        right: window.innerWidth,
                        bottom: window.innerHeight,
                    };
                } else if (reference === 'parent') {
                    referenceRect = self.parentElement.getBoundingClientRect();
                } else {
                    referenceRect = document.querySelector(reference).getBoundingClientRect();
                }
            } else {
                referenceRect = reference.getBoundingClientRect();
            }

            if (event) {
                evtX = event.touches ? event.touches[0].clientX : event.clientX;
                evtY = event.touches ? event.touches[0].clientY : event.clientY;
                evtXparent = evtX - referenceRect.left;
                evtYparent = evtY - referenceRect.top;
            }

            let overlapsX = panelRect.left < referenceRect.right && panelRect.right > referenceRect.left,
                overlapsY = panelRect.top < referenceRect.bottom && panelRect.bottom > referenceRect.top,
                overlaps = overlapsX && overlapsY;

            return {
                overlaps: overlaps,
                top: panelRect.top - referenceRect.top - parentBorderWidth.top,
                right: referenceRect.right - panelRect.right - parentBorderWidth.right,
                bottom: referenceRect.bottom - panelRect.bottom - parentBorderWidth.bottom,
                left: panelRect.left - referenceRect.left - parentBorderWidth.left,
                parentBorderWidth: parentBorderWidth,
                panelRect: panelRect,
                referenceRect: referenceRect,
                pointer: {
                    // pointer coords relative to window and reference
                    clientX: evtX,
                    clientY: evtY,
                    left: evtXparent - parentBorderWidth.left,
                    top: evtYparent - parentBorderWidth.top,
                    right: referenceRect.width - evtXparent - parentBorderWidth.right,
                    bottom: referenceRect.height - evtYparent - parentBorderWidth.bottom,
                },
            };
        };
        self.setSize = () => {
            if (options.panelSize) {
                const values = jsPanel.pOsize(self, options.panelSize);
                self.style.width = values.width;
                self.style.height = values.height;
            } else if (options.contentSize) {
                const values = jsPanel.pOsize(self, options.contentSize);
                self.content.style.width = values.width;
                self.content.style.height = values.height;
                self.style.width = values.width; // explicitly assign current width/height to panel
                self.content.style.width = '100%';
            }
            return self;
        };
        self.resize = (...params) => {
            let dimensions = window.getComputedStyle(self),
                size = { width: dimensions.width, height: dimensions.height },
                updateCache = true,
                callback;
            params.forEach(value => {
                if (typeof value === 'string') {
                    size = value;
                } else if (typeof value === 'object') {
                    size = Object.assign(size, value);
                } else if (typeof value === 'boolean') {
                    updateCache = value;
                } else if (typeof value === 'function') {
                    callback = value;
                }
            });

            let values = jsPanel.pOsize(self, size);
            self.style.width = values.width;
            self.style.height = values.height;

            // check whether self has docked panels -> reposition docked panels
            if (self.slaves && self.slaves.size > 0) {
                self.slaves.forEach(slave => slave.reposition());
            }

            if (updateCache) {
                self.saveCurrentDimensions();
            }
            self.status = 'normalized';
            let smallifyBtn = self.controlbar.querySelector('.jsPanel-btn-smallify');
            if (smallifyBtn) {
                smallifyBtn.style.transform = 'rotate(0deg)';
            }
            if (callback) {
                callback.call(self, self);
            }
            self.calcSizeFactors();
            return self;
        };
        self.windowResizeHandler = e => {
            if (e.target === window) {
                // see https://bugs.jqueryui.com/ticket/7514
                let status = self.status,
                    onWindowResize = options.onwindowresize,
                    left,
                    top;
                if (status === 'maximized' && onWindowResize) {
                    self.maximize(false, true);
                } else if (self.snapped && status !== 'minimized') {
                    self.snap(self.snapped, true);
                } else if (status === 'normalized' || status === 'smallified' || status === 'maximized') {
                    let settingType = typeof onWindowResize;
                    if (settingType === 'boolean') {
                        left = (window.innerWidth - self.offsetWidth) * self.hf;
                        self.style.left = left <= 0 ? 0 : left + 'px';
                        top = (window.innerHeight - self.offsetHeight) * self.vf;
                        self.style.top = top <= 0 ? 0 : top + 'px';
                    } else if (settingType === 'function') {
                        onWindowResize.call(self, e, self);
                    } else if (settingType === 'object') {
                        // { preset: boolean, callback: function(event, panel){} }
                        if (onWindowResize.preset === true) {
                            left = (window.innerWidth - self.offsetWidth) * self.hf;
                            self.style.left = left <= 0 ? 0 : left + 'px';
                            top = (window.innerHeight - self.offsetHeight) * self.vf;
                            self.style.top = top <= 0 ? 0 : top + 'px';
                            onWindowResize.callback.call(self, e, self);
                        } else {
                            onWindowResize.callback.call(self, e, self);
                        }
                    }
                } else if (status === 'smallifiedmax' && onWindowResize) {
                    self.maximize(false, true).smallify();
                }
                // check whether self has docked panels -> reposition docked panels as well
                if (self.slaves && self.slaves.size > 0) {
                    self.slaves.forEach(slave => slave.reposition());
                }
            }
        };

        self.setControls = (sel, cb) => {
            self.header.querySelectorAll('.jsPanel-btn').forEach(item => {
                const btnClass = item.className.split('-'),
                    btn = btnClass[btnClass.length - 1];
                if (self.getAttribute(`data-btn${btn}`) !== 'hidden') {
                    item.style.display = 'block';
                }
            });
            sel.forEach(item => {
                const btn = self.controlbar.querySelector(item);
                if (btn) {
                    btn.style.display = 'none';
                }
            });
            if (cb) {
                cb.call(self, self);
            }
            return self;
        };
        self.setControlStatus = (ctrl, action = 'enable', cb) => {
            const btn = self.controlbar.querySelector(`.jsPanel-btn-${ctrl}`);
            switch (action) {
                case 'disable':
                    if (self.getAttribute(`data-btn${ctrl}`) !== 'removed') {
                        self.setAttribute(`data-btn${ctrl}`, 'disabled');
                        btn.style.pointerEvents = 'none';
                        btn.style.opacity = 0.4;
                        btn.style.cursor = 'default';
                    }
                    break;
                case 'hide':
                    if (self.getAttribute(`data-btn${ctrl}`) !== 'removed') {
                        self.setAttribute(`data-btn${ctrl}`, 'hidden');
                        btn.style.display = 'none';
                    }
                    break;
                case 'show':
                    if (self.getAttribute(`data-btn${ctrl}`) !== 'removed') {
                        self.setAttribute(`data-btn${ctrl}`, 'enabled');
                        btn.style.display = 'block';
                        btn.style.pointerEvents = 'auto';
                        btn.style.opacity = 1;
                        btn.style.cursor = 'pointer';
                    }
                    break;
                case 'enable':
                    if (self.getAttribute(`data-btn${ctrl}`) !== 'removed') {
                        if (self.getAttribute(`data-btn${ctrl}`) === 'hidden') {
                            btn.style.display = 'block';
                        }
                        self.setAttribute(`data-btn${ctrl}`, 'enabled');
                        btn.style.pointerEvents = 'auto';
                        btn.style.opacity = 1;
                        btn.style.cursor = 'pointer';
                    }
                    break;
                case 'remove':
                    self.controlbar.removeChild(btn);
                    self.setAttribute(`data-btn${ctrl}`, 'removed');
                    break;
            }

            if (cb) {
                cb.call(self, self);
            }
            return self;
        };
        self.setControlSize = ctrlSize => {
            // does not work with Font Awesome webfont (only svg/js)
            const size = ctrlSize.toLowerCase(),
                icons = self.controlbar.querySelectorAll('.jsPanel-btn');
            icons.forEach(icon => {
                ['jsPanel-btn-xl', 'jsPanel-btn-lg', 'jsPanel-btn-md', 'jsPanel-btn-sm', 'jsPanel-btn-xs'].forEach(item => icon.classList.remove(item));
                icon.classList.add(`jsPanel-btn-${size}`);
            });
            // adjust font-size of title
            if (size === 'xl') {
                self.titlebar.style.fontSize = '1.5rem';
            } else if (size === 'lg') {
                self.titlebar.style.fontSize = '1.25rem';
            } else if (size === 'md') {
                self.titlebar.style.fontSize = '1.05rem';
            } else if (size === 'sm') {
                self.titlebar.style.fontSize = '.9rem';
            } else if (size === 'xs') {
                self.titlebar.style.fontSize = '.8rem';
            }
        };
        self.setHeaderControls = cb => {
            // add custom controls
            if (self.options.headerControls.add) {
                let customControls = self.options.headerControls.add;
                if (!Array.isArray(customControls)) {
                    // if options.addControls is not an array make it one
                    customControls = [customControls];
                }
                customControls.forEach(ctrl => self.addControl(ctrl));
            }
            // get all control names including controls added with option.addControls
            let controls = [],
                ctrls = self.controlbar.querySelectorAll('.jsPanel-btn');
            ctrls.forEach(ctrl => {
                let match = ctrl.className.match(/jsPanel-btn-[a-z\d]{3,}/i),
                    ctrlName = match[0].substring(12);
                controls.push(ctrlName);
            });
            // normalize option headerControls and reset it accordingly
            const option = jsPanel.pOheaderControls(options.headerControls);
            options.headerControls = option;
            // set status of controls
            controls.forEach(item => {
                if (option[item]) {
                    self.setControlStatus(item, option[item]);
                }
            });
            // set size of controls
            self.setControlSize(option.size);
            if (cb) {
                cb.call(self, self);
            }
            return self;
        };
        self.setHeaderLogo = (logo, cb) => {
            let logos = [self.headerlogo],
                minPanel = document.querySelector('#' + self.id + '-min');
            if (minPanel) {
                logos.push(minPanel.querySelector('.jsPanel-headerlogo'));
            }

            if (typeof logo === 'string') {
                if (!logo.startsWith('<')) {
                    // is assumed to be an img url
                    logos.forEach(item => {
                        jsPanel.emptyNode(item);
                        let img = document.createElement('img');
                        img.src = logo;
                        item.append(img);
                    });
                } else {
                    logos.forEach(item => item.innerHTML = logo);
                }
            } else {
                // assumed to be a node object
                logos.forEach(item => {
                    jsPanel.emptyNode(item);
                    item.append(logo);
                });
            }
            // images must not be draggable, otherwise dragit interactions locks
            self.headerlogo.childNodes.forEach(logo => {
                if (logo.nodeName && logo.nodeName === 'IMG') {
                    logo.setAttribute('draggable', 'false');
                }
            });

            if (cb) {
                cb.call(self, self);
            }
            return self;
        };
        self.setHeaderRemove = cb => {
            self.removeChild(self.header);
            self.content.classList.add('jsPanel-content-noheader');
            ['close', 'maximize', 'normalize', 'minimize', 'smallify'].forEach(item => self.setAttribute(`data-btn${item}`, 'removed'));

            if (cb) {
                cb.call(self, self);
            }
            return self;
        };
        self.setHeaderTitle = (hdrTitle, cb) => {
            let titles = [self.headertitle],
                minPanel = document.querySelector('#' + self.id + '-min');
            if (minPanel) {
                titles.push(minPanel.querySelector('.jsPanel-title'));
            }
            if (typeof hdrTitle === 'string') {
                titles.forEach(item => item.innerHTML = hdrTitle);
            } else if (typeof hdrTitle === 'function') {
                titles.forEach(item => {
                    jsPanel.emptyNode(item);
                    item.innerHTML = hdrTitle();
                });
            } else {
                // assumed to be a node object
                titles.forEach(item => {
                    jsPanel.emptyNode(item);
                    item.append(hdrTitle);
                });
            }
            if (cb) {
                cb.call(self, self);
            }
            return self;
        };
        self.setIconfont = (font, panel = self, cb) => {
            if (font) {
                let classArray, textArray;
                if (font === 'fa' || font === 'far' || font === 'fal' || font === 'fas' || font === 'fad') {
                    classArray = [
                        `${font} fa-window-close`,
                        `${font} fa-window-maximize`,
                        `${font} fa-window-restore`,
                        `${font} fa-window-minimize`,
                        `${font} fa-chevron-up`,
                    ];
                } else if (font === 'material-icons') {
                    classArray = [font, font, font, font, font, font];
                    textArray = ['close', 'fullscreen', 'fullscreen_exit', 'call_received', 'expand_less'];
                } else if (Array.isArray(font)) {
                    classArray = [
                        `custom-control-icon ${font[4]}`,
                        `custom-control-icon ${font[3]}`,
                        `custom-control-icon ${font[2]}`,
                        `custom-control-icon ${font[1]}`,
                        `custom-control-icon ${font[0]}`,
                    ];
                } else if (font === 'bootstrap' || font === 'glyphicon') {
                    classArray = [
                        'glyphicon glyphicon-remove',
                        'glyphicon glyphicon-fullscreen',
                        'glyphicon glyphicon-resize-full',
                        'glyphicon glyphicon-minus',
                        'glyphicon glyphicon-chevron-up',
                    ];
                } else {
                    return panel;
                }
                panel.querySelectorAll('.jsPanel-controlbar .jsPanel-btn').forEach(item => jsPanel.emptyNode(item).innerHTML = '<span></span>');
                Array.prototype.slice
                    .call(panel.querySelectorAll('.jsPanel-controlbar .jsPanel-btn > span'))
                    .reverse()
                    .forEach((item, i) => {
                        item.className = classArray[i];
                        if (font === 'material-icons') {
                            item.textContent = textArray[i];
                        }
                    });
            }
            if (cb) {
                cb.call(panel, panel);
            }
            return panel;
        };
        self.addToolbar = (place, tb, cb) => {
            if (place === 'header') {
                place = self.headertoolbar;
            } else if (place === 'footer') {
                place = self.footer;
            }

            if (typeof tb === 'string') {
                place.innerHTML = tb;
            } else if (Array.isArray(tb)) {
                tb.forEach(item => {
                    if (typeof item === 'string') {
                        place.innerHTML += item;
                    } else {
                        place.append(item);
                    }
                });
            } else if (typeof tb === 'function') {
                let tool = tb.call(self, self);
                if (typeof tool === 'string') {
                    place.innerHTML = tool;
                } else {
                    place.append(tool);
                }
            } else {
                place.append(tb);
            }

            place.classList.add('active');
            if (cb) {
                cb.call(self, self);
            }
            return self;
        };
        self.addCloseControl = () => {
            let ctrl = document.createElement('button'),
                colorContent = self.content.style.color;
            ctrl.classList.add('jsPanel-addCloseCtrl');
            ctrl.innerHTML = jsPanel.icons.close;
            ctrl.style.color = colorContent;
            if (self.options.rtl) {
                ctrl.classList.add('rtl');
            }
            self.appendChild(ctrl);
            jsPanel.pointerup.forEach(evt => {
                ctrl.addEventListener(evt, e => {
                    e.preventDefault();
                    // disable close for all mouse buttons but left
                    if (e.button && e.button > 0) {
                        return false;
                    }
                    self.close(null, true);
                });
            });
            // pointerdown handler needed to prevent side effect with resize handles
            jsPanel.pointerdown.forEach(evt => {
                ctrl.addEventListener(evt, e => e.preventDefault());
            });
            return self;
        };
        self.addControl = obj => {
            // obj.name - string, the name of the control, results for example in "jsPanel-btn jsPanel-btn-menu"
            // obj.html - string, the html of the control to add, for example "<span class='fa fa-menu'></span>"
            // obj.handler - function(panel, control), the event handler to assign to the new control
            // obj.position - number, the position within the controlbar where the control is to be added
            if (!obj.html) {
                return self;
            }
            if (!obj.position) {
                obj.position = 1;
            }

            const count = self.controlbar.querySelectorAll('.jsPanel-btn').length;
            let control = document.createElement('button');
            control.innerHTML = obj.html;
            control.className = `jsPanel-btn jsPanel-btn-${obj.name} jsPanel-btn-${options.headerControls.size}`;
            control.style.color = self.header.style.color;
            if (obj.position > count) {
                // new control is the first from right (default ltr text-direction) or the first from left (if option rtl is used)
                self.controlbar.append(control);
            } else {
                self.controlbar.insertBefore(control, self.querySelector(`.jsPanel-controlbar .jsPanel-btn:nth-child(${obj.position})`));
            }
            const ariaLabel = obj.ariaLabel || obj.name;
            if (ariaLabel) {
                control.setAttribute('aria-label', ariaLabel);
            }

            jsPanel.pointerup.forEach(evt => {
                control.addEventListener(evt, e => {
                    e.preventDefault();
                    if (e.button && e.button > 0) {
                        return false;
                    }
                    obj.handler.call(self, self, control);
                });
            });
            if (obj.afterInsert) {
                obj.afterInsert.call(control, control);
            }
            return self;
        };
        self.setRtl = () => {
            [self.header, self.content, self.footer].forEach(item => {
                item.dir = 'rtl';
                if (options.rtl.lang) {
                    item.lang = options.rtl.lang;
                }
            });
        };

        // option.id
        self.id = options.id;
        // option.paneltype classname
        self.classList.add('jsPanel-' + options.paneltype);

        // set z-index and paneltype class
        if (options.paneltype === 'standard') {
            self.style.zIndex = this.zi.next();
        }

        // option.container
        panelContainer.append(self);
        self.front(false, false); // just to ensure iframe code in self.front() works for very first panel as well, second false prevents onfronted callbacks to be executed
        // option.theme
        self.setTheme(options.theme);

        // option.boxShadow
        if (options.boxShadow) {
            self.classList.add(`jsPanel-depth-${options.boxShadow}`);
        }

        /* option.header, option.iconfont, option.headerControls, option.headerLogo, option.headerTitle */
        if (options.header) {
            if (options.headerLogo) {
                self.setHeaderLogo(options.headerLogo);
            }
            self.setIconfont(options.iconfont);
            self.setHeaderTitle(options.headerTitle);
            self.setHeaderControls(); // now handles controls added with option addControls as well

            // compatibility code for IE11 due to flex bug - https://caniuse.com/#feat=flexbox
            if (jsPanel.isIE) {
                let bars = [self.headerbar, self.controlbar];
                switch (self.options.headerControls.size) {
                    case 'md':
                        bars.forEach(bar => {
                            bar.style.height = '34px';
                        });
                        break;
                    case 'xs':
                        bars.forEach(bar => {
                            bar.style.height = '26px';
                        });
                        break;
                    case 'sm':
                        bars.forEach(bar => {
                            bar.style.height = '30px';
                        });
                        break;
                    case 'lg':
                        bars.forEach(bar => {
                            bar.style.height = '38px';
                        });
                        break;
                    case 'xl':
                        bars.forEach(bar => {
                            bar.style.height = '42px';
                        });
                        break;
                }
            }
            // end - - - - - - - - -

            if (options.header === 'auto-show-hide') {
                let boxShadow = 'jsPanel-depth-' + options.boxShadow;
                self.header.style.opacity = 0;
                self.style.backgroundColor = 'transparent';
                this.remClass(self, boxShadow);
                this.setClass(self.content, boxShadow);
                self.header.addEventListener('mouseenter', () => {
                    self.header.style.opacity = 1;
                    jsPanel.setClass(self, boxShadow);
                    jsPanel.remClass(self.content, boxShadow);
                });
                self.header.addEventListener('mouseleave', () => {
                    self.header.style.opacity = 0;
                    jsPanel.remClass(self, boxShadow);
                    jsPanel.setClass(self.content, boxShadow);
                });
            }
        } else {
            self.setHeaderRemove();
            if (options.addCloseControl) {
                self.addCloseControl();
            }
        }

        // option.headerToolbar
        if (options.headerToolbar) {
            self.addToolbar(self.headertoolbar, options.headerToolbar);
        }
        // option.footerToolbar
        if (options.footerToolbar) {
            self.addToolbar(self.footer, options.footerToolbar);
        }

        // option.border
        if (options.border) {
            self.setBorder(options.border);
        }
        // option.borderRadius
        if (options.borderRadius) {
            self.setBorderRadius(options.borderRadius);
        }

        // option.css - add custom css classes to the panel html
        if (options.css) {
            for (const [selector, classname] of Object.entries(options.css)) {
                // option is a string used to build the desired class selector like `.jsPanel-${option}` except for the outermost DIV where option must be simply 'panel'
                // value is a string with either a single class name or a space separated list of class names like 'classA classB classC'
                if (selector === 'panel') {
                    // handles the special case outermost DIV of the panel
                    self.className += ` ${classname}`; // don't remove space at the beginning of template string
                } else {
                    // handles all other elements within the panel template
                    let elmt = self.querySelector(`.jsPanel-${selector}`);
                    if (elmt) {
                        elmt.className += ` ${classname}`; // don't remove space at the beginning of template string
                    }
                }
            }
        }

        // option.content
        if (options.content) {
            if (typeof options.content === 'function') {
                options.content.call(self, self);
            } else if (typeof options.content === 'string') {
                self.content.innerHTML = options.content;
            } else {
                self.content.append(options.content);
            }
        }

        // option.contentAjax
        if (options.contentAjax) {
            this.ajax(options.contentAjax, self);
        }

        // option.contentFetch
        if (options.contentFetch) {
            this.fetch(options.contentFetch, self);
        }

        // option.contentOverflow
        if (options.contentOverflow) {
            const value = options.contentOverflow.split(' ');
            if (value.length === 1) {
                self.content.style.overflow = value[0];
            } else if (value.length === 2) {
                self.content.style.overflowX = value[0];
                self.content.style.overflowY = value[1];
            }
        }

        // option.autoclose -- should be before option.size
        if (options.autoclose) {
            if (typeof options.autoclose === 'number') {
                options.autoclose = { time: options.autoclose + 'ms' };
            } else if (typeof options.autoclose === 'string') {
                options.autoclose = { time: options.autoclose };
            }
            let autoclose = Object.assign({}, jsPanel.defaultAutocloseConfig, options.autoclose);
            if (autoclose.time && typeof autoclose.time === 'number') {
                autoclose.time += 'ms';
            }
            let slider = self.progressbar.querySelector('div');
            slider.addEventListener('animationend', e => {
                e.stopPropagation();
                self.progressbar.classList.remove('active');
                self.close();
            });
            if (autoclose.progressbar) {
                self.progressbar.classList.add('active');
                if (autoclose.background) {
                    if (jsPanel.colorNames[autoclose.background]) {
                        self.progressbar.style.background = '#' + jsPanel.colorNames[autoclose.background];
                    } else {
                        self.progressbar.style.background = autoclose.background;
                    }
                } else {
                    self.progressbar.classList.add('success-bg'); // default background for progressbar
                }
            }
            slider.style.animation = `${autoclose.time} progressbar`;
        }

        // option.rtl
        if (options.rtl) {
            self.setRtl();
        }
        // option.size -- should be after option.theme
        self.setSize();

        // option.position
        self.status = 'normalized';
        // if option.position evaluates to false panel will not be positioned at all
        if (options.position) {
            this.position(self, options.position);
        } else {
            self.style.opacity = 1;
        }
        document.dispatchEvent(jspanelnormalized);
        self.calcSizeFactors();

        // option.animateIn
        if (options.animateIn) {
            // remove class again on animationend, otherwise opacity doesn't change when panel is dragged
            self.addEventListener('animationend', () => {
                this.remClass(self, options.animateIn);
            });
            this.setClass(self, options.animateIn);
        }

        // option.dragit AND option.resizeit AND option.syncMargins
        if (options.syncMargins) {
            let containment = this.pOcontainment(options.maximizedMargin);
            if (options.dragit) {
                options.dragit.containment = containment;
                if (options.dragit.snap === true) {
                    // options.dragit.snap must be object in order to set options.dragit.snap.containment = true;
                    options.dragit.snap = jsPanel.defaultSnapConfig;
                    options.dragit.snap.containment = true;
                } else if (options.dragit.snap) {
                    options.dragit.snap.containment = true;
                }
            }
            if (options.resizeit) {
                options.resizeit.containment = containment;
            }
        }

        if (options.dragit) {
            // callbacks must be an array of function(s) in order to be able to dynamically add/remove callbacks (for example in extensions)
            ['start', 'drag', 'stop'].forEach(item => {
                if (options.dragit[item]) {
                    if (typeof options.dragit[item] === 'function') {
                        options.dragit[item] = [options.dragit[item]];
                    }
                } else {
                    options.dragit[item] = [];
                }
            });
            self.drag(options.dragit);
            // do not use self.options.dragit.stop.push() !!!
            self.addEventListener('jspaneldragstop', e => {
                if (e.panel === self) {self.calcSizeFactors();}
            }, false);
        } else {
            self.titlebar.style.cursor = 'default';
        }

        if (options.resizeit) {
            // callbacks must be an array of function(s) in order to be able to dynamically add/remove callbacks (for example in extensions)
            ['start', 'resize', 'stop'].forEach(item => {
                if (options.resizeit[item]) {
                    if (typeof options.resizeit[item] === 'function') {
                        options.resizeit[item] = [options.resizeit[item]];
                    }
                } else {
                    options.resizeit[item] = [];
                }
            });
            self.sizeit(options.resizeit);
            let startstatus = void 0;
            // do not use self.options.resizeit.start.push() !!!
            self.addEventListener('jspanelresizestart', e => {
                if (e.panel === self) {startstatus = self.status;}
            }, false);
            // do not use self.options.resizeit.stop.push() !!!
            self.addEventListener(
                'jspanelresizestop',
                e => {
                    if (e.panel === self) {
                        if (
                            (startstatus === 'smallified' || startstatus === 'smallifiedmax' || startstatus === 'maximized') &&
                            parseFloat(self.style.height) > parseFloat(window.getComputedStyle(self.header).height)
                        ) {
                            self.setControls(['.jsPanel-btn-normalize']);
                            self.status = 'normalized';
                            document.dispatchEvent(jspanelnormalized);
                            document.dispatchEvent(jspanelstatuschange);
                            if (options.onstatuschange) {
                                jsPanel.processCallbacks(self, options.onstatuschange, 'every');
                            }
                            self.calcSizeFactors();
                        }
                    }
                },
                false
            );
        }

        // initialize self.currentData - must be after options position & size
        //self.saveCurrentDimensions(true); // not clear why 'true' was added here or why this param is needed at all in the method
        self.saveCurrentDimensions();
        self.saveCurrentPosition();

        // option.setStatus
        if (options.setStatus) {
            if (options.setStatus === 'smallifiedmax') {
                self.maximize().smallify();
            } else if (options.setStatus === 'smallified') {
                self.smallify();
            } else {
                // remove last char ('d') from end of string to get function name to call
                self[options.setStatus.slice(0, -1)]();
            }
        }

        // front panel on mousedown
        this.pointerdown.forEach(item => {
            self.addEventListener(
                item,
                e => {
                    if (!e.target.closest('.jsPanel-btn-close') && !e.target.closest('.jsPanel-btn-minimize') && options.paneltype === 'standard') {
                        self.front();
                    }
                },
                false
            );
        });

        // option onwindowresize
        if (options.onwindowresize) {
            // if container is 'window'
            if (self.options.container === 'window') {
                window.addEventListener('resize', self.windowResizeHandler, false);
            }
        }

        // option onparentresize
        if (options.onparentresize) {
            let onResize = options.onparentresize,
                settingType = typeof onResize,
                parentPanel = self.isChildpanel();
            if (parentPanel) {
                const parentContainer = parentPanel.content;
                let parentContainerSize = [];
                self.parentResizeHandler = e => {
                    // if resized panel is the parent panel of the one whose option onContentResize is set to true
                    if (e.panel === parentPanel) {
                        // get dimensions of parent panel's content section
                        parentContainerSize[0] = parentContainer.offsetWidth;
                        parentContainerSize[1] = parentContainer.offsetHeight;
                        let status = self.status,
                            left,
                            top;
                        if (status === 'maximized' && onResize) {
                            self.maximize();
                        } else if (self.snapped && status !== 'minimized') {
                            self.snap(self.snapped, true);
                        } else if (status === 'normalized' || status === 'smallified' || status === 'maximized') {
                            if (settingType === 'function') {
                                onResize.call(self, self, {
                                    width: parentContainerSize[0],
                                    height: parentContainerSize[1],
                                });
                            } else if (settingType === 'object' && onResize.preset === true) {
                                left = (parentContainerSize[0] - self.offsetWidth) * self.hf;
                                self.style.left = left <= 0 ? 0 : left + 'px';
                                top = (parentContainerSize[1] - self.offsetHeight) * self.vf;
                                self.style.top = top <= 0 ? 0 : top + 'px';
                                onResize.callback.call(self, self, {
                                    width: parentContainerSize[0],
                                    height: parentContainerSize[1],
                                });
                            } else if (settingType === 'boolean') {
                                left = (parentContainerSize[0] - self.offsetWidth) * self.hf;
                                self.style.left = left <= 0 ? 0 : left + 'px';
                                top = (parentContainerSize[1] - self.offsetHeight) * self.vf;
                                self.style.top = top <= 0 ? 0 : top + 'px';
                            }
                        } else if (status === 'smallifiedmax' && onResize) {
                            self.maximize().smallify();
                        }
                    }
                };
                document.addEventListener('jspanelresize', self.parentResizeHandler, false);
            }
        }

        // global callbacks
        if (this.globalCallbacks) {
            if (Array.isArray(this.globalCallbacks)) {
                this.globalCallbacks.forEach(item => item.call(self, self));
            } else {
                this.globalCallbacks.call(self, self);
            }
        }

        // option.callback
        if (options.callback) {
            if (Array.isArray(options.callback)) {
                options.callback.forEach(item => item.call(self, self));
            } else {
                options.callback.call(self, self);
            }
        }

        // constructor callback
        // if (cb) {cb.call(self, self);}
        if (cb) {
            if (Array.isArray(cb)) {
                cb.forEach(item => item.call(self, self));
            } else {
                cb.call(self, self);
            }
        }

        document.dispatchEvent(jspanelloaded);
        return self;
    },
};
